From b37a672157e4081d4041f366040219219bc113e6 Mon Sep 17 00:00:00 2001 From: Liordino Neto Date: Tue, 4 Feb 2020 17:13:20 -0300 Subject: [PATCH] feat: created the base skeleton for the Coin ATM Radar Setup screen (#355) For now it doesn't have any functionality, CSS styling, or tables feat: added tables to the Coin ATM Radar screen No styles or icons yet. feat: added Edit links to the Coin ATM Radar Setup screen chore: added some TODOs to the Coin ATM Radar Setup screen refactor: make a function to render both Coin ATM Radar Setup tables feat: toggle function on table (not working on table content yet) feat: added toggle state to the Share Information switch feat: make the Share Information toggle change the tables contents feat: added a 'Help' link Later will be replaced by a '?' button with a pop-up help text. feat: added radio groups when in editing mode of Coin ATM Radar screen chore: replaced edit and help placeholders with proper buttons style: defined some CSS styles feat: added a help popper feat: enable radio change events, so they can be selected style: defined row styles for the radio button groups For now the help popper contains only a placeholder text, which must be replaced. refactor: replaced help and edit Links with Buttons fix: fixed the margin spacing for buttons style: added more spacing between before the share information Switch refactor: made a function to check a table element boolean value fix: changed some typography styles for titles (from TL2 to H4) style: added the alternating row style to the information tables style: added true and false table icons replacing the placeholder texts style: fixed the radio groups right margin refactor: renamed everything from CoinATMRadarSetup to CoinATMRadar feat: integrate share information switch with graphql api Still needs to finish the tables api integration. refactor: rename properties from 'show' to 'send' feat: integrate tables with graphql api feat: updated the coinatmradar module with the new config properties feat: added logic to show only currently available CAR config properties feat: disable the edit button when 'Share Information' is set to 'No' chore: replaced the lorem ipsum with the correct text on the help popup fix: fixed some css styling errors chore: added link to the CAR page on the corresponding button refactor: refactored the information table to make a component out of it refactor: changed the name of the 'active' property of the properties table to 'disabled' and inverted it's logic refactor: created a component from the boolean property table refactor: delete repeated styling code on the car page and it's table style: update styles to be in accordance with the guidelines refactor: rename properties to make them more concise fix: readded the old coinatmradar module and renamed the current one refactor: replaced ternary if with a coalescing operator fix: make the info table always visible, and it's values not dependent on the disabled status fix: move link style to jss refactor: simplify the use of car properties, and remove currently unused ones fix: put hooks on their correct places fix: when changing the value of the switch, update only it's own config fix: rename booleanPropertiesTable file so it starts with a capital letter. chore: readd removed properties on the car settings (all commented out) chore: integrated CAR settings page into Operator Info page fix: replaced broken white and disabled versions of the edit icon --- lib/coinatmradar/new-coinatmradar.js | 178 ++++++++++++++++ .../BooleanPropertiesTable.js | 112 ++++++++++ .../BooleanPropertiesTable.styles.js | 68 ++++++ .../booleanPropertiesTable/index.js | 3 + .../OperatorInfo/CoinATMRadar/CoinATMRadar.js | 193 ++++++++++++++++++ .../CoinATMRadar/CoinATMRadar.styles.js | 31 +++ .../pages/OperatorInfo/CoinATMRadar/index.js | 3 + .../src/pages/OperatorInfo/OperatorInfo.js | 2 + .../styling/icons/action/edit/disabled.svg | 15 +- .../src/styling/icons/action/edit/white.svg | 15 +- .../src/styling/icons/table/false.svg | 12 ++ .../src/styling/icons/table/true.svg | 9 + 12 files changed, 619 insertions(+), 22 deletions(-) create mode 100644 lib/coinatmradar/new-coinatmradar.js create mode 100644 new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js create mode 100644 new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.styles.js create mode 100644 new-lamassu-admin/src/components/booleanPropertiesTable/index.js create mode 100644 new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js create mode 100644 new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.styles.js create mode 100644 new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/index.js create mode 100644 new-lamassu-admin/src/styling/icons/table/false.svg create mode 100644 new-lamassu-admin/src/styling/icons/table/true.svg diff --git a/lib/coinatmradar/new-coinatmradar.js b/lib/coinatmradar/new-coinatmradar.js new file mode 100644 index 00000000..5c590d11 --- /dev/null +++ b/lib/coinatmradar/new-coinatmradar.js @@ -0,0 +1,178 @@ +const axios = require('axios') +const _ = require('lodash/fp') +const hkdf = require('futoin-hkdf') + +const pify = require('pify') +const fs = pify(require('fs')) + +const db = require('../db') +const mnemonicHelpers = require('../mnemonic-helpers') +const configManager = require('../config-manager') +const options = require('../options') +const logger = require('../logger') +const plugins = require('../plugins') + +const TIMEOUT = 10000 +const MAX_CONTENT_LENGTH = 2000 + +// How long a machine can be down before it's considered offline +const STALE_INTERVAL = '2 minutes' + +module.exports = { update, mapRecord } + +function mapCoin (info, deviceId, settings, cryptoCode) { + const config = info.config + const rates = plugins(settings, deviceId).buildRates(info.rates)[cryptoCode] || { cashIn: null, cashOut: null } + const cryptoConfig = configManager.scoped(cryptoCode, deviceId, config) + const unscoped = configManager.unscoped(config) + const showCommissions = unscoped.coinAtmRadar.sendCommissions + + const cashInFee = showCommissions ? cryptoConfig.cashInCommission / 100 : null + const cashOutFee = showCommissions ? cryptoConfig.cashOutCommission / 100 : null + const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', rates) : null + const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', rates) : null + + return { + cryptoCode, + cashInFee, + cashOutFee, + cashInRate, + cashOutRate + } +} + +function mapIdentification (info, deviceId) { + const machineConfig = configManager.machineScoped(deviceId, info.config) + + return { + isPhone: machineConfig.smsVerificationActive, + isPalmVein: false, + isPhoto: false, + isIdDocScan: machineConfig.idCardDataVerificationActive, + isFingerprint: false + } +} + +function mapMachine (info, settings, machineRow) { + const deviceId = machineRow.device_id + const config = info.config + const unscoped = configManager.unscoped(config) + const machineConfig = configManager.machineScoped(deviceId, config) + + const lastOnline = machineRow.last_online.toISOString() + const status = machineRow.stale ? 'online' : 'offline' + const showSupportedCryptocurrencies = + unscoped.coinAtmRadar.sendSupportedCryptocurrencies + const showSupportedFiat = + unscoped.coinAtmRadar.sendSupportedFiat + const showSupportedBuySellDirection = + unscoped.coinAtmRadar.sendSupportedBuySellDirection + const showLimitsAndVerification = + unscoped.coinAtmRadar.sendLimitsAndVerification + + const cashLimit = showLimitsAndVerification ? ( + machineConfig.hardLimitVerificationActive + ? machineConfig.hardLimitVerificationThreshold + : Infinity ) : null + + const cryptoCurrencies = machineConfig.cryptoCurrencies + const cashInEnabled = showSupportedBuySellDirection ? true : null + const cashOutEnabled = showSupportedBuySellDirection + ? machineConfig.cashOutEnabled + : null + const fiat = showSupportedFiat ? machineConfig.fiatCurrency : null + const identification = mapIdentification(info, deviceId) + const coins = showSupportedCryptocurrencies ? + _.map(_.partial(mapCoin, [info, deviceId, settings]), cryptoCurrencies) + : null + + return { + machineId: deviceId, + address: { + streetAddress: null, + city: null, + region: null, + postalCode: null, + country: null + }, + location: { + name: null, + url: null, + phone: null + }, + status, + lastOnline, + cashIn: cashInEnabled, + cashOut: cashOutEnabled, + manufacturer: 'lamassu', + cashInTxLimit: cashLimit, + cashOutTxLimit: cashLimit, + cashInDailyLimit: cashLimit, + cashOutDailyLimit: cashLimit, + fiatCurrency: fiat, + identification, + coins + } +} + +function getMachines (info, settings) { + const sql = `select device_id, last_online, now() - last_online < $1 as stale from devices + where display=TRUE and + paired=TRUE + order by created` + + return db.any(sql, [STALE_INTERVAL]) + .then(_.map(_.partial(mapMachine, [info, settings]))) +} + +function sendRadar (data) { + const url = _.get(['coinAtmRadar', 'url'], options) + + if (_.isEmpty(url)) { + return Promise.reject(new Error('Missing coinAtmRadar url!')) + } + + const config = { + url, + method: 'post', + data, + timeout: TIMEOUT, + maxContentLength: MAX_CONTENT_LENGTH + } + + console.log('%j', data) + + return axios(config) + .then(r => console.log(r.status)) +} + +function mapRecord (info, settings) { + const timestamp = new Date().toISOString() + return Promise.all([getMachines(info, settings), fs.readFile(options.mnemonicPath, 'utf8')]) + .then(([machines, mnemonic]) => { + return { + operatorId: computeOperatorId(mnemonicHelpers.toEntropyBuffer(mnemonic)), + operator: { + name: null, + phone: null, + email: null + }, + timestamp, + machines + } + }) +} + +function update (info, settings) { + const config = configManager.unscoped(info.config) + + if (!config.coinAtmRadar.active) return Promise.resolve() + + return mapRecord(info, settings) + .then(sendRadar) + .catch(err => logger.error(`Failure to update CoinATMRadar`, err)) +} + +function computeOperatorId (masterSeed) { + return hkdf(masterSeed, 16, { salt: 'lamassu-server-salt', info: 'operator-id' }).toString('hex') +} diff --git a/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js new file mode 100644 index 00000000..306e5efe --- /dev/null +++ b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js @@ -0,0 +1,112 @@ +import { makeStyles } from '@material-ui/core/styles' +import React, { useState, memo } from 'react' + +import { H4 } from 'src/components/typography' +import { Link } from 'src/components/buttons' +import { RadioGroup } from 'src/components/inputs' +import { Table, TableBody, TableRow, TableCell } from 'src/components/table' +import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' +import { ReactComponent as EditIconDisabled } from 'src/styling/icons/action/edit/disabled.svg' +import { ReactComponent as TrueIcon } from 'src/styling/icons/table/true.svg' +import { ReactComponent as FalseIcon } from 'src/styling/icons/table/false.svg' + +import { booleanPropertiesTableStyles } from './BooleanPropertiesTable.styles' + +const useStyles = makeStyles(booleanPropertiesTableStyles) + +const BooleanPropertiesTable = memo( + ({ title, disabled, data, elements, save }) => { + const [editing, setEditing] = useState(false) + const [radioGroupValues, setRadioGroupValues] = useState(elements) + + const classes = useStyles() + + const innerSave = () => { + radioGroupValues.forEach(element => { + data[element.name] = element.value + }) + + save(data) + setEditing(false) + } + + const innerCancel = () => { + setEditing(false) + } + + const handleRadioButtons = (elementName, newValue) => { + setRadioGroupValues( + radioGroupValues.map(element => + element.name === elementName + ? { ...element, value: newValue } + : element + ) + ) + } + + const radioButtonOptions = [ + { label: 'Yes', value: true }, + { label: 'No', value: false } + ] + + if (!elements || radioGroupValues?.length === 0) return null + + return ( +
+
+

{title}

+ {editing ? ( +
+ + Cancel + + + Save + +
+ ) : ( +
+ +
+ )} +
+ + + {radioGroupValues && + radioGroupValues.map((element, idx) => ( + + + {element.display} + {editing ? ( + + handleRadioButtons( + element.name, + event.target.value === 'true' + ) + } + className={classes.radioButtons} + /> + ) : element.value ? ( + + ) : ( + + )} + + + ))} + +
+
+ ) + } +) + +export default BooleanPropertiesTable diff --git a/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.styles.js b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.styles.js new file mode 100644 index 00000000..18a1d49d --- /dev/null +++ b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.styles.js @@ -0,0 +1,68 @@ +import baseStyles from 'src/pages/Logs.styles' +import { tableCellColor, zircon } from 'src/styling/variables' + +const { fillColumn } = baseStyles + +const booleanPropertiesTableStyles = { + booleanPropertiesTableWrapper: { + display: 'flex', + flexDirection: 'column', + width: 396 + }, + tableRow: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + '&:nth-child(even)': { + backgroundColor: tableCellColor + }, + '&:nth-child(odd)': { + backgroundColor: zircon + }, + boxShadow: '0 0 0 0 rgba(0, 0, 0, 0)' + }, + tableCell: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + width: '100%', + height: 32, + padding: [[5, 14, 5, 20]] + }, + transparentButton: { + '& > *': { + margin: 'auto 12px' + }, + '& button': { + border: 'none', + backgroundColor: 'transparent', + cursor: 'pointer' + } + }, + rowWrapper: { + display: 'flex', + alignItems: 'center', + position: 'relative', + flex: 'wrap' + }, + rightAligned: { + display: 'flex', + position: 'absolute', + right: 0 + }, + radioButtons: { + display: 'flex', + flexDirection: 'row', + marginRight: -15 + }, + rightLink: { + marginLeft: '20px' + }, + fillColumn, + popoverContent: { + width: 272, + padding: [[10, 15]] + } +} + +export { booleanPropertiesTableStyles } diff --git a/new-lamassu-admin/src/components/booleanPropertiesTable/index.js b/new-lamassu-admin/src/components/booleanPropertiesTable/index.js new file mode 100644 index 00000000..3112ae66 --- /dev/null +++ b/new-lamassu-admin/src/components/booleanPropertiesTable/index.js @@ -0,0 +1,3 @@ +import BooleanPropertiesTable from './BooleanPropertiesTable' + +export { BooleanPropertiesTable } diff --git a/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js new file mode 100644 index 00000000..02bffbc1 --- /dev/null +++ b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js @@ -0,0 +1,193 @@ +import { makeStyles } from '@material-ui/core/styles' +import React, { useState, memo } from 'react' +import { useQuery, useMutation } from '@apollo/react-hooks' +import { gql } from 'apollo-boost' + +import { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable' +import { H4, P, Label2 } from 'src/components/typography' +import { Button } from 'src/components/buttons' +import Popper from 'src/components/Popper' +import { Switch } from 'src/components/inputs' +import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg' + +import { mainStyles } from './CoinATMRadar.styles' + +const useStyles = makeStyles(mainStyles) + +const initialValues = { + active: false, + // location: false, + commissions: false, + supportedCryptocurrencies: false, + supportedFiat: false, + supportedBuySellDirection: false, + limitsAndVerification: false + // operatorName: false, + // operatorPhoneNumber: false, + // operatorEmail: false +} + +const GET_CONFIG = gql` + { + config + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` + +const CoinATMRadar = memo(() => { + const [helpPopperAnchorEl, setHelpPopperAnchorEl] = useState(null) + const [coinAtmRadarConfig, setCoinAtmRadarConfig] = useState(null) + + const classes = useStyles() + + // TODO: treat errors on useMutation and useQuery + const [saveConfig] = useMutation(SAVE_CONFIG, { + onCompleted: configResponse => + setCoinAtmRadarConfig(configResponse.saveConfig.coinAtmRadar) + }) + useQuery(GET_CONFIG, { + onCompleted: configResponse => { + setCoinAtmRadarConfig( + configResponse?.config?.coinAtmRadar ?? initialValues + ) + } + }) + + const save = it => saveConfig({ variables: { config: { coinAtmRadar: it } } }) + + const handleOpenHelpPopper = event => { + setHelpPopperAnchorEl(helpPopperAnchorEl ? null : event.currentTarget) + } + + const handleCloseHelpPopper = () => { + setHelpPopperAnchorEl(null) + } + + const helpPopperOpen = Boolean(helpPopperAnchorEl) + + if (!coinAtmRadarConfig) return null + + return ( + <> +
+
+ + + +
+
+
+

Coin ATM Radar share settings

+
+ +
+
+
+

Share information?

+
+ + save({ + active: event.target.checked + }) + } + /> +
+ {coinAtmRadarConfig.active ? 'Yes' : 'No'} +
+ + {/* */} + + ) +}) + +export default CoinATMRadar diff --git a/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.styles.js b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.styles.js new file mode 100644 index 00000000..ab0302d2 --- /dev/null +++ b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.styles.js @@ -0,0 +1,31 @@ +import baseStyles from 'src/pages/Logs.styles' +import { booleanPropertiesTableStyles } from 'src/components/booleanPropertiesTable/BooleanPropertiesTable.styles' + +const { button } = baseStyles +const { rowWrapper, rightAligned } = booleanPropertiesTableStyles + +const mainStyles = { + button, + transparentButton: { + '& > *': { + margin: 'auto 15px' + }, + '& button': { + border: 'none', + backgroundColor: 'transparent', + cursor: 'pointer' + } + }, + rowWrapper, + switchWrapper: { + display: 'flex', + marginLeft: 120 + }, + rightAligned, + popoverContent: { + width: 272, + padding: [[10, 15]] + } +} + +export { mainStyles } diff --git a/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/index.js b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/index.js new file mode 100644 index 00000000..8fe804d8 --- /dev/null +++ b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/index.js @@ -0,0 +1,3 @@ +import CoinATMRadar from './CoinATMRadar' + +export default CoinATMRadar diff --git a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js index ec31ff8d..2e7c45b8 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js @@ -7,6 +7,7 @@ import Title from 'src/components/Title' import logsStyles from '../Logs.styles' +import CoinAtmRadar from './CoinATMRadar' import ContactInfo from './ContactInfo' const localStyles = { @@ -49,6 +50,7 @@ const OperatorInfo = () => { />
{isSelected(CONTACT_INFORMATION) && } + {isSelected(COIN_ATM_RADAR) && }
diff --git a/new-lamassu-admin/src/styling/icons/action/edit/disabled.svg b/new-lamassu-admin/src/styling/icons/action/edit/disabled.svg index 6b10746a..19ba8772 100644 --- a/new-lamassu-admin/src/styling/icons/action/edit/disabled.svg +++ b/new-lamassu-admin/src/styling/icons/action/edit/disabled.svg @@ -1,17 +1,10 @@ - + icon/action/edit/disabled Created with Sketch. - - - - - - - - - - + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/action/edit/white.svg b/new-lamassu-admin/src/styling/icons/action/edit/white.svg index bf023ef6..a4a16c85 100644 --- a/new-lamassu-admin/src/styling/icons/action/edit/white.svg +++ b/new-lamassu-admin/src/styling/icons/action/edit/white.svg @@ -1,17 +1,10 @@ - + icon/action/edit/white Created with Sketch. - - - - - - - - - - + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/table/false.svg b/new-lamassu-admin/src/styling/icons/table/false.svg new file mode 100644 index 00000000..e06867c2 --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/table/false.svg @@ -0,0 +1,12 @@ + + + + icon/table/false + Created with Sketch. + + + + + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/icons/table/true.svg b/new-lamassu-admin/src/styling/icons/table/true.svg new file mode 100644 index 00000000..d805af9d --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/table/true.svg @@ -0,0 +1,9 @@ + + + + icon/table/true + Created with Sketch. + + + + \ No newline at end of file