From 1f6d272aa08ad973ec52626cb11e811eb6dbbfae Mon Sep 17 00:00:00 2001 From: Taranto Date: Tue, 7 Apr 2020 19:03:18 +0100 Subject: [PATCH] fix: rework wallet screen --- lib/new-admin/accounts.js | 124 ----- lib/new-admin/config/accounts.js | 12 +- lib/new-admin/graphql/schema.js | 18 +- lib/new-admin/machines.js | 23 +- lib/new-settings-loader.js | 47 +- new-lamassu-admin/src/App.js | 2 +- .../src/components/LogsDownloaderPopper.js | 4 +- new-lamassu-admin/src/components/Modal.js | 72 ++- .../src/components/{Stage.js => Stepper.js} | 14 +- .../BooleanPropertiesTable.js | 4 +- .../src/components/buttons/Button.js | 8 +- .../src/components/buttons/Button.styles.js | 7 +- .../src/components/buttons/IconButton.js | 18 +- .../src/components/dataTable/DataTable.js | 90 ---- .../src/components/dataTable/index.js | 3 - .../src/components/editableTable/Context.js | 3 + .../src/components/editableTable/Header.js | 28 +- .../editableTable/NamespacedTable.js | 29 ++ .../src/components/editableTable/Row.js | 126 +++-- .../src/components/editableTable/Table.js | 179 ++++--- .../src/components/editableTable/consts.js | 4 - .../src/components/editableTable/index.js | 3 +- .../src/components/fake-table/Table.js | 10 +- .../src/components/fake-table/Table.styles.js | 16 +- .../inputs/autocomplete/Autocomplete.js | 121 ----- .../inputs/autocomplete/AutocompleteSelect.js | 121 ----- .../components/inputs/autocomplete/commons.js | 132 ----- .../components/inputs/base/Autocomplete.js | 39 +- .../src/components/inputs/base/Radio.js | 14 - .../src/components/inputs/base/RadioGroup.js | 77 ++- .../src/components/inputs/base/SecretInput.js | 36 ++ .../src/components/inputs/base/index.js | 3 +- .../components/inputs/formik/Autocomplete.js | 9 +- .../components/inputs/formik/RadioGroup.js | 46 +- .../components/inputs/formik/SecretInput.js | 51 +- .../src/components/inputs/index.js | 13 +- .../src/components/{ => layout}/Header.js | 4 +- .../components/{ => layout}/Header.styles.js | 3 +- .../src/components/layout/Section.js | 12 +- .../src/components/layout/Section.styles.js | 3 - .../src/components/{ => layout}/Sidebar.js | 0 .../components/{ => layout}/Sidebar.styles.js | 3 +- .../src/components/layout/TitleSection.js | 6 +- .../components/layout/TitleSection.styles.js | 7 +- .../single-row-table/SingleRowTable.js | 154 ++---- .../single-row-table/SingleRowTable.styles.js | 41 ++ .../ExpTable.js => tables/DataTable.js} | 130 +++-- .../src/components/tables/DataTable.styles.js | 20 + .../src/components/tables/Stripes.js | 12 + .../src/pages/Customers/Customers.js | 2 +- new-lamassu-admin/src/pages/Funding.js | 2 +- .../src/pages/Locales/Locales.js | 16 +- new-lamassu-admin/src/pages/Locales/helper.js | 169 ++++--- new-lamassu-admin/src/pages/MachineLogs.js | 2 +- .../src/pages/Notifications/Notifications.js | 14 +- .../Notifications/components/EditHeader.js | 6 +- .../sections/CryptoBalanceOverrides.js | 31 +- .../sections/FiatBalanceOverrides.js | 19 +- .../src/pages/Notifications/sections/Setup.js | 14 +- .../OperatorInfo/CoinATMRadar/CoinATMRadar.js | 10 +- .../src/pages/OperatorInfo/OperatorInfo.js | 2 +- new-lamassu-admin/src/pages/Services/Bitgo.js | 257 ---------- .../src/pages/Services/Bitstamp.js | 123 ----- .../src/pages/Services/Blockcypher.js | 113 ----- .../src/pages/Services/EditService.js | 94 ---- .../src/pages/Services/FormRenderer.js | 67 +++ .../src/pages/Services/Infura.js | 126 ----- new-lamassu-admin/src/pages/Services/Itbit.js | 133 ------ .../src/pages/Services/Kraken.js | 108 ----- .../src/pages/Services/Mailgun.js | 139 ------ .../src/pages/Services/Services.js | 293 +++--------- .../src/pages/Services/Services.styles.js | 177 ------- .../src/pages/Services/Strike.js | 86 ---- .../src/pages/Services/Twilio.js | 138 ------ new-lamassu-admin/src/pages/Services/aux.js | 44 -- .../src/pages/Services/schemas/bitgo.js | 120 +++++ .../src/pages/Services/schemas/bitstamp.js | 43 ++ .../src/pages/Services/schemas/blockcypher.js | 34 ++ .../src/pages/Services/schemas/index.js | 21 + .../src/pages/Services/schemas/infura.js | 41 ++ .../src/pages/Services/schemas/itbit.js | 50 ++ .../src/pages/Services/schemas/kraken.js | 32 ++ .../src/pages/Services/schemas/mailgun.js | 49 ++ .../src/pages/Services/schemas/strike.js | 23 + .../src/pages/Services/schemas/twilio.js | 48 ++ .../src/pages/Transactions/Transactions.js | 21 +- new-lamassu-admin/src/pages/Wallet/Wallet.js | 99 ++++ .../src/pages/Wallet/WalletSettings.js | 451 ------------------ new-lamassu-admin/src/pages/Wallet/Wizard.js | 383 ++++----------- .../src/pages/Wallet/WizardSplash.js | 67 ++- .../src/pages/Wallet/WizardStep.js | 155 ++++++ .../src/pages/Wallet/WizardStep.styles.js | 42 ++ new-lamassu-admin/src/pages/Wallet/aux.js | 21 - new-lamassu-admin/src/pages/Wallet/helper.js | 103 ++++ .../src/pages/maintenance/MachineStatus.js | 14 +- new-lamassu-admin/src/routing/routes.js | 2 +- new-lamassu-admin/src/stories/index.js | 4 +- new-lamassu-admin/src/styling/global/index.js | 4 + .../src/styling/icons/stripes.svg | 28 ++ .../src/styling/logos/icon-bitcoin-colour.svg | 11 +- new-lamassu-admin/src/styling/theme.js | 12 +- new-lamassu-admin/src/utils/config.js | 11 +- new-lamassu-admin/src/utils/string.js | 11 +- 103 files changed, 2094 insertions(+), 3892 deletions(-) delete mode 100644 lib/new-admin/accounts.js rename new-lamassu-admin/src/components/{Stage.js => Stepper.js} (88%) delete mode 100644 new-lamassu-admin/src/components/dataTable/DataTable.js delete mode 100644 new-lamassu-admin/src/components/dataTable/index.js create mode 100644 new-lamassu-admin/src/components/editableTable/Context.js create mode 100644 new-lamassu-admin/src/components/editableTable/NamespacedTable.js delete mode 100644 new-lamassu-admin/src/components/editableTable/consts.js delete mode 100644 new-lamassu-admin/src/components/inputs/autocomplete/Autocomplete.js delete mode 100644 new-lamassu-admin/src/components/inputs/autocomplete/AutocompleteSelect.js delete mode 100644 new-lamassu-admin/src/components/inputs/autocomplete/commons.js delete mode 100644 new-lamassu-admin/src/components/inputs/base/Radio.js create mode 100644 new-lamassu-admin/src/components/inputs/base/SecretInput.js rename new-lamassu-admin/src/components/{ => layout}/Header.js (96%) rename new-lamassu-admin/src/components/{ => layout}/Header.styles.js (97%) rename new-lamassu-admin/src/components/{ => layout}/Sidebar.js (100%) rename new-lamassu-admin/src/components/{ => layout}/Sidebar.styles.js (95%) create mode 100644 new-lamassu-admin/src/components/single-row-table/SingleRowTable.styles.js rename new-lamassu-admin/src/components/{expandable-table/ExpTable.js => tables/DataTable.js} (52%) create mode 100644 new-lamassu-admin/src/components/tables/DataTable.styles.js create mode 100644 new-lamassu-admin/src/components/tables/Stripes.js delete mode 100644 new-lamassu-admin/src/pages/Services/Bitgo.js delete mode 100644 new-lamassu-admin/src/pages/Services/Bitstamp.js delete mode 100644 new-lamassu-admin/src/pages/Services/Blockcypher.js delete mode 100644 new-lamassu-admin/src/pages/Services/EditService.js create mode 100644 new-lamassu-admin/src/pages/Services/FormRenderer.js delete mode 100644 new-lamassu-admin/src/pages/Services/Infura.js delete mode 100644 new-lamassu-admin/src/pages/Services/Itbit.js delete mode 100644 new-lamassu-admin/src/pages/Services/Kraken.js delete mode 100644 new-lamassu-admin/src/pages/Services/Mailgun.js delete mode 100644 new-lamassu-admin/src/pages/Services/Services.styles.js delete mode 100644 new-lamassu-admin/src/pages/Services/Strike.js delete mode 100644 new-lamassu-admin/src/pages/Services/Twilio.js delete mode 100644 new-lamassu-admin/src/pages/Services/aux.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/bitgo.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/bitstamp.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/blockcypher.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/index.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/infura.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/itbit.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/kraken.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/mailgun.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/strike.js create mode 100644 new-lamassu-admin/src/pages/Services/schemas/twilio.js create mode 100644 new-lamassu-admin/src/pages/Wallet/Wallet.js delete mode 100644 new-lamassu-admin/src/pages/Wallet/WalletSettings.js create mode 100644 new-lamassu-admin/src/pages/Wallet/WizardStep.js create mode 100644 new-lamassu-admin/src/pages/Wallet/WizardStep.styles.js delete mode 100644 new-lamassu-admin/src/pages/Wallet/aux.js create mode 100644 new-lamassu-admin/src/pages/Wallet/helper.js create mode 100644 new-lamassu-admin/src/styling/icons/stripes.svg diff --git a/lib/new-admin/accounts.js b/lib/new-admin/accounts.js deleted file mode 100644 index fd6624ef..00000000 --- a/lib/new-admin/accounts.js +++ /dev/null @@ -1,124 +0,0 @@ -const _ = require('lodash/fp') - -const db = require('../db') -const config = require('./config') -const ph = require('../plugin-helper') - -const schemas = ph.loadSchemas() - -function fetchAccounts () { - return db.oneOrNone('select data from user_config where type=$1', ['accounts']) - .then(row => { - // Hard code this for now - const accounts = [{ - code: 'blockcypher', - display: 'Blockcypher', - fields: [ - { code: 'confidenceFactor', display: 'Confidence Factor', fieldType: 'integer', required: true, value: 40 } - ] - }] - - return row - ? Promise.resolve(row.data.accounts) - : db.none('insert into user_config (type, data, valid) values ($1, $2, $3)', ['accounts', { accounts }, true]) - .then(fetchAccounts) - }) -} - -function selectedAccounts () { - const mapAccount = v => v.fieldLocator.fieldType === 'account' && - v.fieldValue.value - - const mapSchema = code => schemas[code] - return config.fetchConfig() - .then(conf => { - const accountCodes = _.uniq(conf.map(mapAccount) - .filter(_.identity)) - - return _.sortBy(_.get('display'), accountCodes.map(mapSchema) - .filter(_.identity)) - }) -} - -function fetchAccountSchema (account) { - return schemas[account] -} - -function mergeAccount (oldAccount, newAccount) { - if (!newAccount) return oldAccount - - const newFields = newAccount.fields - - const updateWithData = oldField => { - const newField = _.find(r => r.code === oldField.code, newFields) - const newValue = _.isUndefined(newField) ? oldField.value : newField.value - return _.set('value', newValue, oldField) - } - - const updatedFields = oldAccount.fields.map(updateWithData) - - return _.set('fields', updatedFields, oldAccount) -} - -function getAccounts (accountCode) { - const schema = fetchAccountSchema(accountCode) - if (!schema) return Promise.reject(new Error('No schema for: ' + accountCode)) - - return fetchAccounts() - .then(accounts => { - if (_.isEmpty(accounts)) return [schema] - const account = _.find(r => r.code === accountCode, accounts) - const mergedAccount = mergeAccount(schema, account) - - return updateAccounts(mergedAccount, accounts) - }) -} - -function elideSecrets (account) { - const elideSecret = field => { - return field.fieldType === 'password' - ? _.set('value', !_.isEmpty(field.value), field) - : field - } - - return _.set('fields', account.fields.map(elideSecret), account) -} - -function getAccount (accountCode) { - return getAccounts(accountCode) - .then(accounts => _.find(r => r.code === accountCode, accounts)) - .then(elideSecrets) -} - -function save (accounts) { - return db.none('update user_config set data=$1 where type=$2', [{ accounts: accounts }, 'accounts']) -} - -function updateAccounts (newAccount, accounts) { - const accountCode = newAccount.code - const isPresent = _.some(_.matchesProperty('code', accountCode), accounts) - const updateAccount = r => r.code === accountCode - ? newAccount - : r - - return isPresent - ? _.map(updateAccount, accounts) - : _.concat(accounts, newAccount) -} - -function updateAccount (account) { - return getAccounts(account.code) - .then(accounts => { - const merged = mergeAccount(_.find(_.matchesProperty('code', account.code), accounts), account) - return save(updateAccounts(merged, accounts)) - }) - .then(() => getAccount(account.code)) - .catch((err) => console.log(err)) -} - -module.exports = { - selectedAccounts, - getAccount, - updateAccount, - fetchAccounts -} diff --git a/lib/new-admin/config/accounts.js b/lib/new-admin/config/accounts.js index 13e7453a..a972a4e3 100644 --- a/lib/new-admin/config/accounts.js +++ b/lib/new-admin/config/accounts.js @@ -17,7 +17,7 @@ const ACCOUNT_LIST = [ { code: 'bitstamp', display: 'Bitstamp', class: TICKER, cryptos: [BTC, ETH, LTC, BCH] }, { code: 'coinbase', display: 'Coinbase', class: TICKER, cryptos: [BTC, ETH, LTC, BCH] }, { code: 'itbit', display: 'itBit', class: TICKER, cryptos: [BTC] }, - { code: 'mock-ticker', display: 'Mock (Caution!)', class: TICKER, cryptos: ALL_CRYPTOS }, + { code: 'mock-ticker', display: 'Mock (Caution!)', class: TICKER, cryptos: ALL_CRYPTOS, dev: true }, { code: 'bitcoind', display: 'bitcoind', class: WALLET, cryptos: [BTC] }, { code: 'no-layer2', display: 'No Layer 2', class: LAYER_2, cryptos: ALL_CRYPTOS }, { code: 'infura', display: 'Infura', class: WALLET, cryptos: [ETH] }, @@ -30,17 +30,17 @@ const ACCOUNT_LIST = [ { code: 'bitstamp', display: 'Bitstamp', class: EXCHANGE, cryptos: [BTC, ETH, LTC, BCH] }, { code: 'itbit', display: 'itBit', class: EXCHANGE, cryptos: [BTC] }, { code: 'kraken', display: 'Kraken', class: EXCHANGE, cryptos: [BTC, ETH, LTC, DASH, ZEC, BCH] }, - { code: 'mock-wallet', display: 'Mock (Caution!)', class: WALLET, cryptos: ALL_CRYPTOS }, + { code: 'mock-wallet', display: 'Mock (Caution!)', class: WALLET, cryptos: ALL_CRYPTOS, dev: true }, { code: 'no-exchange', display: 'No exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS }, - { code: 'mock-exchange', display: 'Mock exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS }, - { code: 'mock-sms', display: 'Mock SMS', class: SMS }, - { code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER }, + { code: 'mock-exchange', display: 'Mock exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS, dev: true }, + { code: 'mock-sms', display: 'Mock SMS', class: SMS, dev: true }, + { code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER, dev: true }, { code: 'twilio', display: 'Twilio', class: SMS }, { code: 'mailgun', display: 'Mailgun', class: EMAIL }, { code: 'all-zero-conf', display: 'Always 0-conf', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH] }, { code: 'no-zero-conf', display: 'Always 1-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS }, { code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] }, - { code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH] } + { code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH], dev: true } ] module.exports = { ACCOUNT_LIST } diff --git a/lib/new-admin/graphql/schema.js b/lib/new-admin/graphql/schema.js index ef15d7fc..24feee74 100644 --- a/lib/new-admin/graphql/schema.js +++ b/lib/new-admin/graphql/schema.js @@ -17,7 +17,7 @@ const funding = require('../funding') const supervisor = require('../supervisor') const serverLogs = require('../server-logs') const pairing = require('../pairing') -const { accounts, coins, countries, currencies, languages } = require('../config') +const { accounts: accountsConfig, coins, countries, currencies, languages } = require('../config') // TODO why does server logs messages can be null? const typeDefs = gql` @@ -61,7 +61,7 @@ const typeDefs = gql` } type Customer { - name: String! + name: String phone: String totalTxs: Int totalSpent: String @@ -71,7 +71,7 @@ const typeDefs = gql` lastTxClass: String } - type Account { + type AccountConfig { code: String! display: String! class: String! @@ -156,7 +156,7 @@ const typeDefs = gql` countries: [Country] currencies: [Currency] languages: [Language] - accounts: [Account] + accountsConfig: [AccountConfig] cryptoCurrencies: [CryptoCurrency] machines: [Machine] customers: [Customer] @@ -166,6 +166,7 @@ const typeDefs = gql` uptime: [ProcessStatus] serverLogs: [ServerLog] transactions: [Transaction] + accounts: [JSONObject] config: JSONObject } @@ -188,6 +189,8 @@ const typeDefs = gql` serverSupportLogs: SupportLogsResponse saveConfig(config: JSONObject): JSONObject createPairingTotem(name: String!): String + saveAccount(account: JSONObject): [JSONObject] + saveAccounts(accounts: [JSONObject]): [JSONObject] } ` @@ -202,7 +205,7 @@ const resolvers = { countries: () => countries, currencies: () => currencies, languages: () => languages, - accounts: () => accounts, + accountsConfig: () => accountsConfig, cryptoCurrencies: () => coins, machines: () => machineLoader.getMachineNames(), customers: () => customers.getCustomersList(), @@ -212,13 +215,16 @@ const resolvers = { uptime: () => supervisor.getAllProcessInfo(), serverLogs: () => serverLogs.getServerLogs(), transactions: () => transactions.batch(), - config: () => settingsLoader.getConfig() + config: () => settingsLoader.getConfig(), + accounts: () => settingsLoader.getAccounts() }, Mutation: { machineAction: (...[, { deviceId, action }]) => machineAction({ deviceId, action }), machineSupportLogs: (...[, { deviceId }]) => supportLogs.insert(deviceId), createPairingTotem: (...[, { name }]) => pairing.totem(name), serverSupportLogs: () => serverLogs.insert(), + saveAccount: (...[, { account }]) => settingsLoader.saveAccounts([account]), + saveAccounts: (...[, { accounts }]) => settingsLoader.saveAccounts(accounts), saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config) .then(it => { notify() diff --git a/lib/new-admin/machines.js b/lib/new-admin/machines.js index cc264184..7f49956f 100644 --- a/lib/new-admin/machines.js +++ b/lib/new-admin/machines.js @@ -1,20 +1,19 @@ const machineLoader = require('../machine-loader') const { UserInputError } = require('apollo-server-express') -function getMachine(machineId) { - return machineLoader.getMachines() - .then(machines => machines.find(({ deviceId }) => deviceId === machineId)) +function getMachine (machineId) { + return machineLoader.getMachines() + .then(machines => machines.find(({ deviceId }) => deviceId === machineId)) } -function machineAction({ deviceId, action }) { - - return getMachine(deviceId) - .then(machine => { - if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId }) - return machine - }) - .then(machineLoader.setMachine({ deviceId, action })) - .then(getMachine(deviceId)) +function machineAction ({ deviceId, action }) { + return getMachine(deviceId) + .then(machine => { + if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId }) + return machine + }) + .then(machineLoader.setMachine({ deviceId, action })) + .then(getMachine(deviceId)) } module.exports = { machineAction } diff --git a/lib/new-settings-loader.js b/lib/new-settings-loader.js index eca4fbb8..7ae6a3ab 100644 --- a/lib/new-settings-loader.js +++ b/lib/new-settings-loader.js @@ -9,23 +9,46 @@ low(adapter).then(it => { db = it }) -function saveConfig (config) { - const currentState = db.getState() - // TODO this should be _.assign - // change after flattening of schema - const newState = _.mergeWith((objValue, srcValue) => { - if (_.isArray(objValue)) { - return srcValue - } - }, currentState, config) +function replace (array, index, value) { + return array.slice(0, index).concat([value]).concat(array.slice(index + 1)) +} +function replaceOrAdd (accounts, account) { + const index = _.findIndex(['code', account.code], accounts) + return index !== -1 ? replace(accounts, index, account) : _.concat(accounts)(account) +} + +function saveAccounts (accountsToSave) { + const currentState = db.getState() || {} + const accounts = currentState.accounts || [] + + const newAccounts = _.reduce(replaceOrAdd)(accounts)(accountsToSave) + + const newState = _.set('accounts', newAccounts, currentState) db.setState(newState) return db.write() - .then(() => newState) + .then(() => newState.accounts) +} + +function getAccounts () { + const state = db.getState() + return state ? state.accounts : null +} + +function saveConfig (config) { + const currentState = db.getState() || {} + const currentConfig = currentState.config || {} + const newConfig = _.assign(currentConfig, config) + + const newState = _.set('config', newConfig, currentState) + db.setState(newState) + return db.write() + .then(() => newState.config) } function getConfig () { - return db.getState() + const state = db.getState() + return (state && state.config) || {} } -module.exports = { getConfig, saveConfig } +module.exports = { getConfig, saveConfig, saveAccounts, getAccounts } diff --git a/new-lamassu-admin/src/App.js b/new-lamassu-admin/src/App.js index 75bc8ce8..90d4ee2c 100644 --- a/new-lamassu-admin/src/App.js +++ b/new-lamassu-admin/src/App.js @@ -13,7 +13,7 @@ import extendJss from 'jss-plugin-extend' import React from 'react' import { BrowserRouter as Router } from 'react-router-dom' -import Header from './components/Header' +import Header from './components/layout/Header' import { tree, Routes } from './routing/routes' import global from './styling/global' import theme from './styling/theme' diff --git a/new-lamassu-admin/src/components/LogsDownloaderPopper.js b/new-lamassu-admin/src/components/LogsDownloaderPopper.js index b04dfcc0..b97a718e 100644 --- a/new-lamassu-admin/src/components/LogsDownloaderPopper.js +++ b/new-lamassu-admin/src/components/LogsDownloaderPopper.js @@ -191,8 +191,8 @@ const LogsDownloaderPopover = ({ } const radioButtonOptions = [ - { label: 'All logs', value: radioButtonAll }, - { label: 'Date range', value: radioButtonRange } + { display: 'All logs', code: radioButtonAll }, + { display: 'Date range', code: radioButtonRange } ] return ( diff --git a/new-lamassu-admin/src/components/Modal.js b/new-lamassu-admin/src/components/Modal.js index 048586d1..7651be26 100644 --- a/new-lamassu-admin/src/components/Modal.js +++ b/new-lamassu-admin/src/components/Modal.js @@ -2,6 +2,8 @@ import { makeStyles, Modal as MaterialModal, Paper } from '@material-ui/core' import classnames from 'classnames' import React from 'react' +import { IconButton } from 'src/components/buttons' +import { H1 } from 'src/components/typography' import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg' const styles = { @@ -10,42 +12,68 @@ const styles = { justifyContent: 'center', alignItems: 'center' }, - modalContentWrapper: { + wrapper: ({ width }) => ({ + width, display: 'flex', - position: 'relative', + flexDirection: 'column', minHeight: 400, maxHeight: '90vh', overflowY: 'auto', borderRadius: 8, - outline: 0, - '& > div': { - width: '100%' - } + outline: 0 + }), + content: { + width: '100%', + display: 'flex', + flexDirection: 'column', + flex: 1, + padding: [[0, 32]] }, - closeIcon: { - position: 'absolute', - width: 18, - height: 18, + button: { padding: 0, - top: 20, - right: 20 + margin: [[20, 20, 'auto', 'auto']] + }, + header: { + display: 'flex' + }, + title: { + margin: [[28, 0, 8, 32]] } } const useStyles = makeStyles(styles) -const Modal = ({ handleClose, children, className, ...props }) => { - const classes = useStyles() +const Modal = ({ + width, + title, + handleClose, + children, + className, + closeOnEscape, + closeOnBackdropClick, + ...props +}) => { + const classes = useStyles({ width }) + + const innerClose = (evt, reason) => { + if (!closeOnBackdropClick && reason === 'backdropClick') return + if (!closeOnEscape && reason === 'escapeKeyDown') return + handleClose() + } return ( - - - - {children} + + +
+ {title &&

{title}

} + handleClose()}> + + +
+
{children}
) diff --git a/new-lamassu-admin/src/components/Stage.js b/new-lamassu-admin/src/components/Stepper.js similarity index 88% rename from new-lamassu-admin/src/components/Stage.js rename to new-lamassu-admin/src/components/Stepper.js index 916daf4e..273f2653 100644 --- a/new-lamassu-admin/src/components/Stage.js +++ b/new-lamassu-admin/src/components/Stepper.js @@ -59,10 +59,10 @@ const styles = { const useStyles = makeStyles(styles) -const Stage = memo(({ stages, currentStage, color = 'spring', className }) => { - if (currentStage < 1 || currentStage > stages) +const Stepper = memo(({ steps, currentStep, color = 'spring', className }) => { + if (currentStep < 1 || currentStep > steps) throw Error('Value of currentStage is invalid') - if (stages < 1) throw Error('Value of stages is invalid') + if (steps < 1) throw Error('Value of stages is invalid') const classes = useStyles() @@ -80,7 +80,7 @@ const Stage = memo(({ stages, currentStage, color = 'spring', className }) => { return (
- {R.range(1, currentStage).map(idx => ( + {R.range(1, currentStep).map(idx => (
{idx > 1 &&
}
@@ -90,13 +90,13 @@ const Stage = memo(({ stages, currentStage, color = 'spring', className }) => {
))}
- {currentStage > 1 &&
} + {currentStep > 1 &&
}
{color === 'spring' && } {color === 'zodiac' && }
- {R.range(currentStage + 1, stages + 1).map(idx => ( + {R.range(currentStep + 1, steps + 1).map(idx => (
@@ -109,4 +109,4 @@ const Stage = memo(({ stages, currentStage, color = 'spring', className }) => { ) }) -export default Stage +export default Stepper diff --git a/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js index 724fb483..296648c4 100644 --- a/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js +++ b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js @@ -44,8 +44,8 @@ const BooleanPropertiesTable = memo( } const radioButtonOptions = [ - { label: 'Yes', value: true }, - { label: 'No', value: false } + { display: 'Yes', code: true }, + { display: 'No', code: false } ] if (!elements || radioGroupValues?.length === 0) return null diff --git a/new-lamassu-admin/src/components/buttons/Button.js b/new-lamassu-admin/src/components/buttons/Button.js index aae47837..bd088448 100644 --- a/new-lamassu-admin/src/components/buttons/Button.js +++ b/new-lamassu-admin/src/components/buttons/Button.js @@ -9,9 +9,11 @@ const useStyles = makeStyles(styles) const ActionButton = memo(({ size = 'lg', children, className, ...props }) => { const classes = useStyles({ size }) return ( - +
+ +
) }) diff --git a/new-lamassu-admin/src/components/buttons/Button.styles.js b/new-lamassu-admin/src/components/buttons/Button.styles.js index 958a38c0..ce8c1cf7 100644 --- a/new-lamassu-admin/src/components/buttons/Button.styles.js +++ b/new-lamassu-admin/src/components/buttons/Button.styles.js @@ -1,3 +1,4 @@ +import typographyStyles from 'src/components/typography/styles' import { white, disabledColor, @@ -6,7 +7,6 @@ import { secondaryColorDarker, spacer } from 'src/styling/variables' -import typographyStyles from 'src/components/typography/styles' const { h3 } = typographyStyles @@ -21,6 +21,11 @@ const pickSize = size => { } export default { + wrapper: ({ size }) => { + const height = pickSize(size) + const shadowSize = height / 12 + return { height: height + shadowSize / 2 } + }, button: ({ size }) => { const height = pickSize(size) const shadowSize = height / 12 diff --git a/new-lamassu-admin/src/components/buttons/IconButton.js b/new-lamassu-admin/src/components/buttons/IconButton.js index 42e68747..f12a90d3 100644 --- a/new-lamassu-admin/src/components/buttons/IconButton.js +++ b/new-lamassu-admin/src/components/buttons/IconButton.js @@ -1,8 +1,15 @@ -import { makeStyles, IconButton as IconB, SvgIcon } from '@material-ui/core' +import { makeStyles, IconButton as IconB } from '@material-ui/core' import React from 'react' const styles = { + label: ({ size }) => ({ + width: size, + height: size + }), root: { + '&svg': { + viewbox: null + }, '&:hover': { backgroundColor: 'inherit' } @@ -11,15 +18,16 @@ const styles = { const useStyles = makeStyles(styles) -const IconButton = ({ children, onClick, ...props }) => { - const classes = useStyles() +const IconButton = ({ size, children, onClick, ...props }) => { + const classes = useStyles({ size }) return ( - {children} + {children} ) } diff --git a/new-lamassu-admin/src/components/dataTable/DataTable.js b/new-lamassu-admin/src/components/dataTable/DataTable.js deleted file mode 100644 index 56fcf116..00000000 --- a/new-lamassu-admin/src/components/dataTable/DataTable.js +++ /dev/null @@ -1,90 +0,0 @@ -import React, { memo } from 'react' -import { - AutoSizer, - List, - CellMeasurer, - CellMeasurerCache -} from 'react-virtualized' - -import { THead, Th, Tr, Td } from 'src/components/fake-table/Table' -import { mainWidth } from 'src/styling/variables' - -const DataTable = memo(({ elements, data }) => { - const cache = new CellMeasurerCache({ - defaultHeight: 62, - fixedWidth: true - }) - - return ( - <> -
- - {elements.map( - ({ width, size, className, textAlign, header }, idx) => ( - - {header} - - ) - )} - -
-
- - {({ height }) => ( - ( - -
- - {elements.map( - ( - { - size, - width, - className, - textAlign, - view = it => it?.toString() - }, - idx - ) => ( - - {view(data[index])} - - ) - )} - -
-
- )} - overscanRowCount={50} - deferredMeasurementCache={cache} - /> - )} -
-
- - ) -}) - -export default DataTable diff --git a/new-lamassu-admin/src/components/dataTable/index.js b/new-lamassu-admin/src/components/dataTable/index.js deleted file mode 100644 index 08a94ecf..00000000 --- a/new-lamassu-admin/src/components/dataTable/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import DataTable from './DataTable' - -export { DataTable } diff --git a/new-lamassu-admin/src/components/editableTable/Context.js b/new-lamassu-admin/src/components/editableTable/Context.js new file mode 100644 index 00000000..f54c8c4d --- /dev/null +++ b/new-lamassu-admin/src/components/editableTable/Context.js @@ -0,0 +1,3 @@ +import React from 'react' + +export default React.createContext() diff --git a/new-lamassu-admin/src/components/editableTable/Header.js b/new-lamassu-admin/src/components/editableTable/Header.js index 74306fb3..b514c3b1 100644 --- a/new-lamassu-admin/src/components/editableTable/Header.js +++ b/new-lamassu-admin/src/components/editableTable/Header.js @@ -1,14 +1,21 @@ -import React from 'react' +import React, { useContext } from 'react' import { Td, THead } from 'src/components/fake-table/Table' import { startCase } from 'src/utils/string' -import { ACTION_COL_SIZE, DEFAULT_COL_SIZE } from './consts' - -const Header = ({ elements, enableEdit, enableDelete }) => { - const actionColSize = - enableDelete && enableEdit ? ACTION_COL_SIZE / 2 : ACTION_COL_SIZE +import TableCtx from './Context' +const Header = () => { + const { + elements, + enableEdit, + editWidth, + enableDelete, + deleteWidth, + enableToggle, + toggleWidth, + DEFAULT_COL_SIZE + } = useContext(TableCtx) return ( {elements.map( @@ -19,15 +26,20 @@ const Header = ({ elements, enableEdit, enableDelete }) => { ) )} {enableEdit && ( - + Edit )} {enableDelete && ( - + Delete )} + {enableToggle && ( + + Enable + + )} ) } diff --git a/new-lamassu-admin/src/components/editableTable/NamespacedTable.js b/new-lamassu-admin/src/components/editableTable/NamespacedTable.js new file mode 100644 index 00000000..4aac1c03 --- /dev/null +++ b/new-lamassu-admin/src/components/editableTable/NamespacedTable.js @@ -0,0 +1,29 @@ +import * as R from 'ramda' +import React from 'react' + +import { fromNamespace, toNamespace } from 'src/utils/config' + +import EditableTable from './Table' + +const NamespacedTable = ({ + name, + save, + data = {}, + namespaces = [], + ...props +}) => { + const innerSave = (...[, it]) => { + save(toNamespace(it.id)(R.omit(['id2'], it))) + } + + const innerData = R.map(it => ({ + id: it, + ...fromNamespace(it)(data) + }))(namespaces) + + return ( + + ) +} + +export default NamespacedTable diff --git a/new-lamassu-admin/src/components/editableTable/Row.js b/new-lamassu-admin/src/components/editableTable/Row.js index c25b1cff..f476afea 100644 --- a/new-lamassu-admin/src/components/editableTable/Row.js +++ b/new-lamassu-admin/src/components/editableTable/Row.js @@ -1,39 +1,46 @@ import { makeStyles } from '@material-ui/core' -import classnames from 'classnames' import { Field, useFormikContext } from 'formik' -import React from 'react' +import * as R from 'ramda' +import React, { useContext } from 'react' import { Link, IconButton } from 'src/components/buttons' import { Td, Tr } from 'src/components/fake-table/Table' +import { Switch } from 'src/components/inputs' import { TL2 } from 'src/components/typography' import { ReactComponent as DisabledDeleteIcon } from 'src/styling/icons/action/delete/disabled.svg' import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg' import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg' import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' +import { ReactComponent as StripesSvg } from 'src/styling/icons/stripes.svg' +import TableCtx from './Context' import styles from './Row.styles' -import { ACTION_COL_SIZE } from './consts' const useStyles = makeStyles(styles) -const ActionCol = ({ - editing, - setEditing, - enableEdit, - disabled, - onDelete, - enableDelete -}) => { +const ActionCol = ({ disabled, editing }) => { const classes = useStyles() const { values, submitForm, resetForm } = useFormikContext() + const { + editWidth, + onEdit, + enableEdit, + enableDelete, + disableRowEdit, + onDelete, + deleteWidth, + enableToggle, + onToggle, + toggleWidth, + actionColSize + } = useContext(TableCtx) - const actionColSize = - enableDelete && enableEdit ? ACTION_COL_SIZE / 2 : ACTION_COL_SIZE + const disableEdit = disabled || (disableRowEdit && disableRowEdit(values)) return ( <> {editing && ( - + )} {!editing && enableEdit && ( - + setEditing && setEditing(values.id)}> - {disabled ? : } + onClick={() => onEdit && onEdit(values.id)}> + {disableEdit ? : } )} {!editing && enableDelete && ( - + onDelete(values.id)}> {disabled ? : } )} + {!editing && enableToggle && ( + + onToggle(values.id)} + /> + + )} ) } @@ -70,6 +87,7 @@ const ECol = ({ editing, config }) => { const { name, input, + editable = true, size, bold, width, @@ -82,11 +100,6 @@ const ECol = ({ editing, config }) => { const { values } = useFormikContext() const classes = useStyles({ textAlign, size }) - const viewClasses = { - [classes.bold]: bold, - [classes.size]: true - } - const iProps = { fullWidth: true, size, @@ -105,43 +118,58 @@ const ECol = ({ editing, config }) => { className={{ [classes.withSuffix]: suffix }} width={width} size={size} + bold={bold} textAlign={textAlign}> - {editing && } - {!editing && values && ( -
{view(values[name])}
+ {editing && editable ? ( + + ) : ( + values && <>{view(values[name])} )} {suffix && {suffix}} ) } -const ERow = ({ - elements, - enableEdit, - enableDelete, - onDelete, - editing, - setEditing, - disabled -}) => { - const { errors } = useFormikContext() +const groupStriped = elements => { + const [toStripe, noStripe] = R.partition(R.has('stripe'))(elements) + if (!toStripe.length) { + return elements + } + + const index = R.indexOf(toStripe[0], elements) + const width = R.compose(R.sum, R.map(R.path(['width'])))(toStripe) + + return R.insert( + index, + { width, editable: false, view: () => }, + noStripe + ) +} + +const ERow = ({ editing, disabled }) => { + const { errors } = useFormikContext() + const { + elements, + enableEdit, + enableDelete, + enableToggle, + stripeWhen + } = useContext(TableCtx) + + const { values } = useFormikContext() + const shouldStripe = stripeWhen && stripeWhen(values) && !editing + + const iElements = shouldStripe ? groupStriped(elements) : elements return ( - {elements.map((it, idx) => ( - - ))} - {(enableEdit || enableDelete) && ( - + {iElements.map((it, idx) => { + return + })} + {(enableEdit || enableDelete || enableToggle) && ( + )} ) diff --git a/new-lamassu-admin/src/components/editableTable/Table.js b/new-lamassu-admin/src/components/editableTable/Table.js index ecc9d57d..5e89ba18 100644 --- a/new-lamassu-admin/src/components/editableTable/Table.js +++ b/new-lamassu-admin/src/components/editableTable/Table.js @@ -7,12 +7,15 @@ import { v4 } from 'uuid' import Link from 'src/components/buttons/Link.js' import { AddButton } from 'src/components/buttons/index.js' import { TBody, Table } from 'src/components/fake-table/Table' -import { Info2 } from 'src/components/typography' +import { Info2, TL1 } from 'src/components/typography' +import TableCtx from './Context' import Header from './Header' import ERow from './Row' import styles from './Table.styles' -import { DEFAULT_COL_SIZE, ACTION_COL_SIZE } from './consts' + +const ACTION_COL_SIZE = 87 +const DEFAULT_COL_SIZE = 100 const useStyles = makeStyles(styles) @@ -24,17 +27,25 @@ const getWidth = R.compose( const ETable = ({ name, title, + titleLg, elements = [], data = [], save, validationSchema, enableCreate, + enableEdit, + editWidth: outerEditWidth, + enableDelete, + deleteWidth = ACTION_COL_SIZE, + enableToggle, + toggleWidth = ACTION_COL_SIZE, + onToggle, forceDisable, disableAdd, - enableDelete, initialValues, - enableEdit, setEditing, + stripeWhen, + disableRowEdit, createText = 'Add override' }) => { const [editingId, setEditingId] = useState(null) @@ -45,7 +56,7 @@ const ETable = ({ const list = index !== -1 ? R.update(index, it, data) : R.prepend(it, data) // no response means the save failed - const response = await save({ [name]: list }) + const response = await save({ [name]: list }, it) if (!response) return setAdding(false) setEditingId(null) @@ -69,83 +80,103 @@ const ETable = ({ const addField = () => setAdding(true) - const actionSize = enableEdit || enableDelete ? ACTION_COL_SIZE : 0 - const width = getWidth(elements) + actionSize + const widthIfEditNull = + enableDelete || enableToggle ? ACTION_COL_SIZE : ACTION_COL_SIZE * 2 + + const editWidth = R.defaultTo(widthIfEditNull)(outerEditWidth) + + const actionColSize = + ((enableDelete && deleteWidth) ?? 0) + + ((enableEdit && editWidth) ?? 0) + + ((enableToggle && toggleWidth) ?? 0) + + const width = getWidth(elements) + actionColSize const classes = useStyles({ width }) const showButtonOnEmpty = !data.length && enableCreate && !adding const canAdd = !forceDisable && !editingId && !disableAdd && !adding const showTable = adding || data.length !== 0 + const ctxValue = { + elements, + enableEdit, + onEdit, + disableRowEdit, + editWidth, + enableDelete, + onDelete, + deleteWidth, + enableToggle, + onToggle, + toggleWidth, + actionColSize, + stripeWhen, + DEFAULT_COL_SIZE + } + return ( -
- {showButtonOnEmpty && ( - - {createText} - - )} - {showTable && ( - <> -
- {title && {title}} - {enableCreate && canAdd && ( - - {createText} - + +
+ {showButtonOnEmpty && ( + + {createText} + + )} + {showTable && ( + <> + {(title || enableCreate) && ( +
+ {title && titleLg && ( + {title} + )} + {title && !titleLg && ( + {title} + )} + {enableCreate && canAdd && ( + + {createText} + + )} +
)} -
- -
-
- {adding && ( - -
- - -
- )} - {data.map((it, idx) => ( - -
- - -
- ))} - -
- - )} -
+ +
+
+ {adding && ( + +
+ + +
+ )} + {data.map((it, idx) => ( + +
+ + +
+ ))} + +
+ + )} +
+ ) } diff --git a/new-lamassu-admin/src/components/editableTable/consts.js b/new-lamassu-admin/src/components/editableTable/consts.js deleted file mode 100644 index da6fd5af..00000000 --- a/new-lamassu-admin/src/components/editableTable/consts.js +++ /dev/null @@ -1,4 +0,0 @@ -const ACTION_COL_SIZE = 175 -const DEFAULT_COL_SIZE = 100 - -export { ACTION_COL_SIZE, DEFAULT_COL_SIZE } diff --git a/new-lamassu-admin/src/components/editableTable/index.js b/new-lamassu-admin/src/components/editableTable/index.js index 602177ed..b9ca2ddb 100644 --- a/new-lamassu-admin/src/components/editableTable/index.js +++ b/new-lamassu-admin/src/components/editableTable/index.js @@ -1,3 +1,4 @@ +import NamespacedTable from './NamespacedTable' import Table from './Table' -export { Table } +export { Table, NamespacedTable } diff --git a/new-lamassu-admin/src/components/fake-table/Table.js b/new-lamassu-admin/src/components/fake-table/Table.js index e6223520..efad75d3 100644 --- a/new-lamassu-admin/src/components/fake-table/Table.js +++ b/new-lamassu-admin/src/components/fake-table/Table.js @@ -32,8 +32,7 @@ const TDoubleLevelHead = ({ children, className }) => { } const TBody = ({ children, className }) => { - const classes = useStyles() - return
{children}
+ return
{children}
} const Td = ({ @@ -42,16 +41,17 @@ const Td = ({ className, width = 100, size, + bold, textAlign, action }) => { - const classes = useStyles({ textAlign, width }) + const classes = useStyles({ textAlign, width, size }) const classNames = { [classes.td]: true, [classes.tdHeader]: header, [classes.actionCol]: action, - [classes.large]: size === 'lg' && !header, - [classes.md]: size === 'md' && !header + [classes.size]: !header, + [classes.bold]: !header && bold } return
{children}
diff --git a/new-lamassu-admin/src/components/fake-table/Table.styles.js b/new-lamassu-admin/src/components/fake-table/Table.styles.js index dd022048..84acf4a9 100644 --- a/new-lamassu-admin/src/components/fake-table/Table.styles.js +++ b/new-lamassu-admin/src/components/fake-table/Table.styles.js @@ -1,4 +1,5 @@ import typographyStyles from 'src/components/typography/styles' +import { bySize, bold } from 'src/styling/helpers' import { tableHeaderColor, tableHeaderHeight, @@ -9,18 +10,11 @@ import { offColor } from 'src/styling/variables' -const { tl1, info2, tl2, p, label1 } = typographyStyles +const { tl2, p, label1 } = typographyStyles export default { - body: { - borderSpacing: [[0, 4]] - }, - large: { - extend: tl1 - }, - md: { - extend: info2 - }, + size: ({ size }) => bySize(size), + bold, header: { extend: tl2, backgroundColor: tableHeaderColor, @@ -79,7 +73,7 @@ export default { mainContent: { display: 'flex', alignItems: 'center', - minHeight: 54 + minHeight: 48 }, // mui-overrides cardContentRoot: { diff --git a/new-lamassu-admin/src/components/inputs/autocomplete/Autocomplete.js b/new-lamassu-admin/src/components/inputs/autocomplete/Autocomplete.js deleted file mode 100644 index 4f5a5d5c..00000000 --- a/new-lamassu-admin/src/components/inputs/autocomplete/Autocomplete.js +++ /dev/null @@ -1,121 +0,0 @@ -import Paper from '@material-ui/core/Paper' -import Popper from '@material-ui/core/Popper' -import { withStyles } from '@material-ui/core/styles' -import Downshift from 'downshift' -import * as R from 'ramda' -import React, { memo, useState } from 'react' - -import { - renderInput, - renderSuggestion, - filterSuggestions, - styles -} from './commons' - -const Autocomplete = memo( - ({ - suggestions, - classes, - placeholder, - label, - itemToString, - code = 'code', - display = 'display', - ...props - }) => { - const { name, value, onBlur } = props.field - const { touched, errors, setFieldValue } = props.form - - const [popperNode, setPopperNode] = useState(null) - - return ( - { - if (itemToString) return itemToString(it) - if (it) return it[display] - return undefined - }} - onChange={it => setFieldValue(name, it)} - defaultHighlightedIndex={0} - selectedItem={value}> - {({ - getInputProps, - getItemProps, - getMenuProps, - isOpen, - inputValue: inputValue2, - selectedItem: selectedItem2, - highlightedIndex, - inputValue, - toggleMenu, - clearSelection - }) => ( -
- {renderInput({ - name, - fullWidth: true, - error: - (touched[`${name}-input`] || touched[name]) && errors[name], - success: - (touched[`${name}-input`] || touched[name] || value) && - !errors[name], - InputProps: getInputProps({ - value: inputValue2 || '', - placeholder, - onBlur, - onClick: event => { - setPopperNode(event.currentTarget.parentElement) - toggleMenu() - }, - onChange: it => { - if (it.target.value === '') { - clearSelection() - } - inputValue = it.target.value - } - }), - label - })} - -
- - {filterSuggestions( - suggestions, - inputValue2, - value ? R.of(value) : [], - code, - display - ).map((suggestion, index) => - renderSuggestion({ - suggestion, - index, - itemProps: getItemProps({ item: suggestion }), - highlightedIndex, - selectedItem: selectedItem2, - code, - display - }) - )} - -
-
-
- )} -
- ) - } -) - -export default withStyles(styles)(Autocomplete) diff --git a/new-lamassu-admin/src/components/inputs/autocomplete/AutocompleteSelect.js b/new-lamassu-admin/src/components/inputs/autocomplete/AutocompleteSelect.js deleted file mode 100644 index e67c78f5..00000000 --- a/new-lamassu-admin/src/components/inputs/autocomplete/AutocompleteSelect.js +++ /dev/null @@ -1,121 +0,0 @@ -import Paper from '@material-ui/core/Paper' -import Popper from '@material-ui/core/Popper' -import { withStyles } from '@material-ui/core/styles' -import classnames from 'classnames' -import Downshift from 'downshift' -import * as R from 'ramda' -import React, { memo, useState } from 'react' - -import { - renderInput, - renderSuggestion, - filterSuggestions, - styles -} from './commons' - -const AutocompleteSelect = memo( - ({ - suggestions, - classes, - placeholder, - label, - itemToString, - code = 'code', - display = 'display', - name, - value, - touched, - error, - handleChange, - className, - ...props - }) => { - const [popperNode, setPopperNode] = useState(null) - - return ( - { - if (itemToString) return itemToString(it) - if (it) return it[display] - return undefined - }} - onChange={handleChange} - defaultHighlightedIndex={0} - selectedItem={value}> - {({ - getInputProps, - getItemProps, - getMenuProps, - isOpen, - inputValue: inputValue2, - selectedItem: selectedItem2, - highlightedIndex, - inputValue, - toggleMenu, - clearSelection - }) => ( -
- {renderInput({ - name, - fullWidth: true, - error: touched && error, - success: touched && !error, - InputProps: getInputProps({ - value: inputValue2 || '', - placeholder, - onClick: event => { - setPopperNode(event.currentTarget.parentElement) - toggleMenu() - }, - onChange: it => { - if (it.target.value === '') { - clearSelection() - } - inputValue = it.target.value - } - }), - label - })} - -
- - {filterSuggestions( - suggestions, - inputValue2, - value ? R.of(value) : [], - code, - display - ).map((suggestion, index) => - renderSuggestion({ - suggestion, - index, - itemProps: getItemProps({ item: suggestion }), - highlightedIndex, - selectedItem: selectedItem2, - code, - display - }) - )} - -
-
-
- )} -
- ) - } -) - -export default withStyles(styles)(AutocompleteSelect) diff --git a/new-lamassu-admin/src/components/inputs/autocomplete/commons.js b/new-lamassu-admin/src/components/inputs/autocomplete/commons.js deleted file mode 100644 index 7f0353c7..00000000 --- a/new-lamassu-admin/src/components/inputs/autocomplete/commons.js +++ /dev/null @@ -1,132 +0,0 @@ -import MenuItem from '@material-ui/core/MenuItem' -import { withStyles } from '@material-ui/core/styles' -import Fuse from 'fuse.js' -import React from 'react' -import slugify from 'slugify' - -import { - fontColor, - inputFontSize, - inputFontWeight, - zircon -} from 'src/styling/variables' -import S from 'src/utils/sanctuary' - -import { TextInput } from '../base' - -function renderInput({ InputProps, error, name, success, ...props }) { - const { onChange, onBlur, value } = InputProps - - return ( - - ) -} - -function renderSuggestion({ - suggestion, - index, - itemProps, - highlightedIndex, - selectedItem, - code, - display -}) { - const isHighlighted = highlightedIndex === index - - const StyledMenuItem = withStyles(theme => ({ - root: { - fontSize: 14, - fontWeight: 400, - color: fontColor - }, - selected: { - '&.Mui-selected, &.Mui-selected:hover': { - fontWeight: 500, - backgroundColor: zircon - } - } - }))(MenuItem) - - return ( - - {suggestion[display]} - - ) -} - -function filterSuggestions( - suggestions = [], - value = '', - currentValues = [], - code, - display -) { - const options = { - shouldSort: true, - threshold: 0.2, - location: 0, - distance: 100, - maxPatternLength: 32, - minMatchCharLength: 1, - keys: [code, display] - } - - const fuse = new Fuse(suggestions, options) - const result = value ? fuse.search(slugify(value, ' ')) : suggestions - - const currentCodes = S.map(S.prop(code))(currentValues) - const filtered = S.filter(it => !S.elem(it[code])(currentCodes))(result) - - const amountToTake = S.min(filtered.length)(5) - - return S.compose(S.fromMaybe([]))(S.take(amountToTake))(filtered) -} - -const styles = theme => ({ - root: { - flexGrow: 1, - height: 250 - }, - container: { - flexGrow: 1, - position: 'relative' - }, - paper: { - // position: 'absolute', - zIndex: 1, - marginTop: theme.spacing(1), - left: 0, - right: 0 - }, - inputRoot: { - fontSize: inputFontSize, - color: fontColor, - fontWeight: inputFontWeight, - flexWrap: 'wrap' - }, - inputInput: { - flex: 1 - }, - success: { - '&:after': { - transform: 'scaleX(1)' - } - }, - divider: { - height: theme.spacing(2) - } -}) - -export { renderInput, renderSuggestion, filterSuggestions, styles } diff --git a/new-lamassu-admin/src/components/inputs/base/Autocomplete.js b/new-lamassu-admin/src/components/inputs/base/Autocomplete.js index b128e16d..4892bd3d 100644 --- a/new-lamassu-admin/src/components/inputs/base/Autocomplete.js +++ b/new-lamassu-admin/src/components/inputs/base/Autocomplete.js @@ -10,34 +10,45 @@ const Autocomplete = ({ limit = 5, options, label, - shouldAdd, - getOptionSelected, - forceShowValue, - value, - onChange, + valueProp, multiple, + onChange, getLabel, + value: outsideValue, error, fullWidth, textAlign, size, ...props }) => { - let iOptions = options + const mapFromValue = options => it => R.find(R.propEq(valueProp, it))(options) + const mapToValue = R.prop(valueProp) - const compare = getOptionSelected || R.equals - const find = R.find(it => compare(value, it)) + const getValue = () => { + if (!valueProp) return outsideValue - if (forceShowValue && !multiple && value && !find(options)) { - iOptions = R.concat(options, [value]) + const transform = multiple + ? R.map(mapFromValue(options)) + : mapFromValue(options) + + return transform(outsideValue) + } + + const value = getValue() + + const iOnChange = (evt, value) => { + if (!valueProp) return onChange(evt, value) + + const rValue = multiple ? R.map(mapToValue)(value) : mapToValue(value) + onChange(evt, rValue) } return ( { return ( - - - ) -} - -export default Radio diff --git a/new-lamassu-admin/src/components/inputs/base/RadioGroup.js b/new-lamassu-admin/src/components/inputs/base/RadioGroup.js index f1635634..6d1e1029 100644 --- a/new-lamassu-admin/src/components/inputs/base/RadioGroup.js +++ b/new-lamassu-admin/src/components/inputs/base/RadioGroup.js @@ -1,64 +1,55 @@ import { - Radio as MaterialRadio, - RadioGroup as MaterialRadioGroup, - FormControlLabel + Radio, + RadioGroup as MRadioGroup, + FormControlLabel, + makeStyles } from '@material-ui/core' -import { withStyles } from '@material-ui/styles' import classnames from 'classnames' import React from 'react' -import { secondaryColor } from '../../../styling/variables' -import typographyStyles from '../../typography/styles' +import { Label1 } from 'src/components/typography' -const { p } = typographyStyles - -const GreenRadio = withStyles({ - root: { - color: secondaryColor, - padding: [[9, 8, 9, 9]], - '&$checked': { - color: secondaryColor - } - }, - checked: {} -})(props => ) - -const Label = withStyles({ +const styles = { label: { - extend: p + height: 16, + lineHeight: '16px', + margin: [[0, 0, 4, 0]], + paddingLeft: 3 } -})(props => ) +} + +const useStyles = makeStyles(styles) -/* options = [{ label, value }] - */ const RadioGroup = ({ name, + label, value, options, - ariaLabel, onChange, className, - ...props + labelClassName, + radioClassName }) => { + const classes = useStyles() + return ( <> - {options && ( - - {options.map((option, idx) => ( - - )} + {label && {label}} + + {options.map((option, idx) => ( + } + label={option.display} + className={classnames(labelClassName)} + /> + ))} + ) } diff --git a/new-lamassu-admin/src/components/inputs/base/SecretInput.js b/new-lamassu-admin/src/components/inputs/base/SecretInput.js new file mode 100644 index 00000000..023772a1 --- /dev/null +++ b/new-lamassu-admin/src/components/inputs/base/SecretInput.js @@ -0,0 +1,36 @@ +import React, { memo, useState } from 'react' + +import { TextInput } from '../base' + +const SecretInput = memo(({ value, onFocus, onBlur, ...props }) => { + const [focused, setFocused] = useState(false) + + const placeholder = '⚬ ⚬ ⚬ This field is set ⚬ ⚬ ⚬' + const previouslyFilled = !!value + const tempValue = previouslyFilled ? '' : value + + const iOnFocus = event => { + setFocused(true) + onFocus && onFocus(event) + } + + const iOnBlur = event => { + setFocused(false) + onBlur && onBlur(event) + } + + return ( + + ) +}) + +export default SecretInput diff --git a/new-lamassu-admin/src/components/inputs/base/index.js b/new-lamassu-admin/src/components/inputs/base/index.js index d51b2770..986275ca 100644 --- a/new-lamassu-admin/src/components/inputs/base/index.js +++ b/new-lamassu-admin/src/components/inputs/base/index.js @@ -1,7 +1,8 @@ import Autocomplete from './Autocomplete' import Checkbox from './Checkbox' import RadioGroup from './RadioGroup' +import SecretInput from './SecretInput' import Switch from './Switch' import TextInput from './TextInput' -export { Checkbox, TextInput, Switch, RadioGroup, Autocomplete } +export { Checkbox, TextInput, Switch, SecretInput, RadioGroup, Autocomplete } diff --git a/new-lamassu-admin/src/components/inputs/formik/Autocomplete.js b/new-lamassu-admin/src/components/inputs/formik/Autocomplete.js index 3b67ca24..16fe6918 100644 --- a/new-lamassu-admin/src/components/inputs/formik/Autocomplete.js +++ b/new-lamassu-admin/src/components/inputs/formik/Autocomplete.js @@ -1,11 +1,17 @@ +import { useFormikContext } from 'formik' +import * as R from 'ramda' import React from 'react' import { Autocomplete } from '../base' -const AutocompleteFormik = props => { +const AutocompleteFormik = ({ options, ...props }) => { const { name, onBlur, value } = props.field const { touched, errors, setFieldValue } = props.form const error = !!(touched[name] && errors[name]) + const { initialValues } = useFormikContext() + + const iOptions = + R.type(options) === 'Function' ? options(initialValues) : options return ( { onBlur={onBlur} value={value} error={error} + options={iOptions} {...props} /> ) diff --git a/new-lamassu-admin/src/components/inputs/formik/RadioGroup.js b/new-lamassu-admin/src/components/inputs/formik/RadioGroup.js index e943d339..3702101c 100644 --- a/new-lamassu-admin/src/components/inputs/formik/RadioGroup.js +++ b/new-lamassu-admin/src/components/inputs/formik/RadioGroup.js @@ -1,42 +1,24 @@ import React, { memo } from 'react' -import { makeStyles } from '@material-ui/core' - -import { Label1 } from 'src/components/typography' import { RadioGroup } from '../base' -const styles = { - label: { - height: 16, - lineHeight: '16px', - margin: [[0, 0, 4, 0]], - paddingLeft: 3 - } -} - -const useStyles = makeStyles(styles) - -const RadioGroupFormik = memo(({ ...props }) => { - const classes = useStyles() - +const RadioGroupFormik = memo(({ label, ...props }) => { const { name, onChange, value } = props.field return ( - <> - {props.label && {props.label}} - { - onChange(e) - props.resetError() - }} - className={props.className} - {...props} - /> - + { + onChange(e) + props.resetError() + }} + className={props.className} + {...props} + /> ) }) diff --git a/new-lamassu-admin/src/components/inputs/formik/SecretInput.js b/new-lamassu-admin/src/components/inputs/formik/SecretInput.js index 9fac6534..59c6d834 100644 --- a/new-lamassu-admin/src/components/inputs/formik/SecretInput.js +++ b/new-lamassu-admin/src/components/inputs/formik/SecretInput.js @@ -1,45 +1,22 @@ -import { makeStyles } from '@material-ui/core' -import classnames from 'classnames' -import React, { memo, useState } from 'react' +import React, { memo } from 'react' -import TextInputFormik from './TextInput' -import { styles } from './TextInput.styles' +import { SecretInput } from '../base' -const useStyles = makeStyles(styles) +const SecretInputFormik = memo(({ ...props }) => { + const { name, onChange, onBlur, value } = props.field + const { touched, errors } = props.form -const SecretInputFormik = memo(({ className, ...props }) => { - const { value } = props.field - - const classes = useStyles() - - const [localTouched, setLocalTouched] = useState(false) - - const handleFocus = event => { - setLocalTouched(true) - props.onFocus() - } - - const spanClass = { - [classes.secretSpan]: true, - [classes.masked]: value && !localTouched, - [classes.hideSpan]: !value || localTouched - } - - const inputClass = { - [classes.maskedInput]: value && !localTouched - } + const error = !!(touched[name] && errors[name]) return ( - <> - - - + ) }) diff --git a/new-lamassu-admin/src/components/inputs/index.js b/new-lamassu-admin/src/components/inputs/index.js index 44389d60..966f52e7 100644 --- a/new-lamassu-admin/src/components/inputs/index.js +++ b/new-lamassu-admin/src/components/inputs/index.js @@ -1,17 +1,8 @@ -import AutocompleteSelect from './autocomplete/AutocompleteSelect' +import Autocomplete from './base/Autocomplete' import Checkbox from './base/Checkbox' -import Radio from './base/Radio' import RadioGroup from './base/RadioGroup' import Select from './base/Select' import Switch from './base/Switch' import TextInput from './base/TextInput' -export { - AutocompleteSelect, - TextInput, - Radio, - Checkbox, - Switch, - Select, - RadioGroup -} +export { Autocomplete, TextInput, Checkbox, Switch, Select, RadioGroup } diff --git a/new-lamassu-admin/src/components/Header.js b/new-lamassu-admin/src/components/layout/Header.js similarity index 96% rename from new-lamassu-admin/src/components/Header.js rename to new-lamassu-admin/src/components/layout/Header.js index 5f3595ee..0a182257 100644 --- a/new-lamassu-admin/src/components/Header.js +++ b/new-lamassu-admin/src/components/layout/Header.js @@ -3,12 +3,12 @@ import classnames from 'classnames' import React, { memo, useState } from 'react' import { NavLink } from 'react-router-dom' +import { Link } from 'src/components/buttons' +import { H4 } from 'src/components/typography' import { ReactComponent as Logo } from 'src/styling/icons/menu/logo.svg' import AddMachine from 'src/pages/AddMachine' import styles from './Header.styles' -import { Link } from './buttons' -import { H4 } from './typography' const useStyles = makeStyles(styles) diff --git a/new-lamassu-admin/src/components/Header.styles.js b/new-lamassu-admin/src/components/layout/Header.styles.js similarity index 97% rename from new-lamassu-admin/src/components/Header.styles.js rename to new-lamassu-admin/src/components/layout/Header.styles.js index 54a750a5..5ba15600 100644 --- a/new-lamassu-admin/src/components/Header.styles.js +++ b/new-lamassu-admin/src/components/layout/Header.styles.js @@ -1,3 +1,4 @@ +import typographyStyles from 'src/components/typography/styles' import { version, mainWidth, @@ -9,8 +10,6 @@ import { fontColor } from 'src/styling/variables' -import typographyStyles from './typography/styles' - const { tl2, p } = typographyStyles let headerHeight = spacer * 7 diff --git a/new-lamassu-admin/src/components/layout/Section.js b/new-lamassu-admin/src/components/layout/Section.js index 5cf8a527..e42c9366 100644 --- a/new-lamassu-admin/src/components/layout/Section.js +++ b/new-lamassu-admin/src/components/layout/Section.js @@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core' import React from 'react' import ErrorMessage from 'src/components/ErrorMessage' -import { TL1 } from 'src/components/typography' +import Subtitle from 'src/components/Subtitle' import styles from './Section.styles' @@ -12,10 +12,12 @@ const Section = ({ error, children, title }) => { const classes = useStyles() return (
-
- {title} - {error && Failed to save changes} -
+ {(title || error) && ( +
+ {title} + {error && Failed to save changes} +
+ )} {children}
) diff --git a/new-lamassu-admin/src/components/layout/Section.styles.js b/new-lamassu-admin/src/components/layout/Section.styles.js index 089af4d7..9898e41c 100644 --- a/new-lamassu-admin/src/components/layout/Section.styles.js +++ b/new-lamassu-admin/src/components/layout/Section.styles.js @@ -1,5 +1,3 @@ -import { offColor } from 'src/styling/variables' - export default { section: { marginBottom: 72 @@ -9,7 +7,6 @@ export default { alignItems: 'center' }, sectionTitle: { - color: offColor, margin: [[16, 20, 23, 0]] } } diff --git a/new-lamassu-admin/src/components/Sidebar.js b/new-lamassu-admin/src/components/layout/Sidebar.js similarity index 100% rename from new-lamassu-admin/src/components/Sidebar.js rename to new-lamassu-admin/src/components/layout/Sidebar.js diff --git a/new-lamassu-admin/src/components/Sidebar.styles.js b/new-lamassu-admin/src/components/layout/Sidebar.styles.js similarity index 95% rename from new-lamassu-admin/src/components/Sidebar.styles.js rename to new-lamassu-admin/src/components/layout/Sidebar.styles.js index c41effd0..a8c4884c 100644 --- a/new-lamassu-admin/src/components/Sidebar.styles.js +++ b/new-lamassu-admin/src/components/layout/Sidebar.styles.js @@ -1,3 +1,4 @@ +import typographyStyles from 'src/components/typography/styles' import { respondTo } from 'src/styling/helpers' import { primaryColor, @@ -7,8 +8,6 @@ import { xxl } from 'src/styling/variables' -import typographyStyles from './typography/styles' - const { tl2, p } = typographyStyles const sidebarColor = zircon diff --git a/new-lamassu-admin/src/components/layout/TitleSection.js b/new-lamassu-admin/src/components/layout/TitleSection.js index 02f547e5..75db7d89 100644 --- a/new-lamassu-admin/src/components/layout/TitleSection.js +++ b/new-lamassu-admin/src/components/layout/TitleSection.js @@ -1,18 +1,22 @@ import { makeStyles } from '@material-ui/core' import React from 'react' +import ErrorMessage from 'src/components/ErrorMessage' import Title from 'src/components/Title' import styles from './TitleSection.styles' const useStyles = makeStyles(styles) -const TitleSection = ({ title }) => { +const TitleSection = ({ title, error }) => { const classes = useStyles() return (
{title} + {error && ( + Failed to save + )}
) diff --git a/new-lamassu-admin/src/components/layout/TitleSection.styles.js b/new-lamassu-admin/src/components/layout/TitleSection.styles.js index db9958b5..7a52e077 100644 --- a/new-lamassu-admin/src/components/layout/TitleSection.styles.js +++ b/new-lamassu-admin/src/components/layout/TitleSection.styles.js @@ -8,10 +8,7 @@ export default { titleAndButtonsContainer: { display: 'flex' }, - iconButton: { - border: 'none', - outline: 0, - backgroundColor: 'transparent', - cursor: 'pointer' + error: { + marginLeft: 12 } } diff --git a/new-lamassu-admin/src/components/single-row-table/SingleRowTable.js b/new-lamassu-admin/src/components/single-row-table/SingleRowTable.js index c52a596f..5df9bbfa 100644 --- a/new-lamassu-admin/src/components/single-row-table/SingleRowTable.js +++ b/new-lamassu-admin/src/components/single-row-table/SingleRowTable.js @@ -2,130 +2,46 @@ import { makeStyles } from '@material-ui/core' import classnames from 'classnames' import React from 'react' -import { Table, THead, TBody, Td, Th } from 'src/components/fake-table/Table' -import typographyStyles from 'src/components/typography/styles' -import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg' -import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/white.svg' -import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg' +import { IconButton } from 'src/components/buttons' import { - offColor, - tableDisabledHeaderColor, - tableNewDisabledHeaderColor, - secondaryColorDarker -} from 'src/styling/variables' + Table, + THead, + TBody, + Td, + Th, + Tr +} from 'src/components/fake-table/Table' +import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/white.svg' -const { label1, p } = typographyStyles +import styles from './SingleRowTable.styles' + +const useStyles = makeStyles(styles) const SingleRowTable = ({ - width = 380, - height = 160, + width = 378, + height = 128, title, items, onEdit, - disabled, - newService, - className, - ...props + className }) => { - const editButtonSize = 54 - - const styles = { - wrapper: { - width: width, - boxShadow: '0 0 4px 0 rgba(0, 0, 0, 0.08)' - }, - buttonTh: { - padding: [[0, 16]] - }, - disabledHeader: { - backgroundColor: tableDisabledHeaderColor, - color: offColor - }, - newDisabledHeader: { - backgroundColor: tableNewDisabledHeaderColor - }, - disabledBody: { - extend: p, - display: 'flex', - alignItems: 'center', - height: 104 - }, - itemWrapper: { - display: 'flex', - flexDirection: 'column', - marginTop: 16, - minHeight: 40, - '& > div:last-child': {} - }, - disabledWrapper: { - display: 'flex', - alignItems: 'center', - '& > span:first-child': { - display: 'flex' - }, - '& > span:last-child': { - paddingLeft: 16 - } - }, - label: { - extend: label1, - color: offColor, - marginBottom: 4 - }, - item: { - extend: p - }, - editButton: { - border: 'none', - backgroundColor: 'transparent', - cursor: 'pointer', - display: 'flex', - padding: 0 - }, - spanNew: { - color: secondaryColorDarker, - marginLeft: 12 - } - } - - const useStyles = makeStyles(styles) - - const classes = useStyles() - - const headerClasses = { - [classes.disabledHeader]: disabled, - [classes.newDisabledHeader]: newService && disabled - } - - const bodyClasses = { - [classes.disabledBody]: disabled - } + const classes = useStyles({ width, height }) return ( <> - {items && ( - - - - - - +
- {title} - {newService && New} - - {!disabled && ( - - )} - {disabled && ( - - )} -
+ + + + + - -
+ {title} + + + +
- {!disabled && ( + {items && ( <> {items[0] && (
@@ -141,18 +57,10 @@ const SingleRowTable = ({ )} )} - {disabled && ( -
- - - - This service is not being used -
- )}
- )} + + + ) } diff --git a/new-lamassu-admin/src/components/single-row-table/SingleRowTable.styles.js b/new-lamassu-admin/src/components/single-row-table/SingleRowTable.styles.js new file mode 100644 index 00000000..f4a5608d --- /dev/null +++ b/new-lamassu-admin/src/components/single-row-table/SingleRowTable.styles.js @@ -0,0 +1,41 @@ +import typographyStyles from 'src/components/typography/styles' +import { offColor } from 'src/styling/variables' + +const { label1, p } = typographyStyles + +export default { + tr: ({ height }) => ({ + margin: 0, + height + }), + table: ({ width }) => ({ + width + }), + head: { + display: 'flex', + flex: 1, + justifyContent: 'space-between', + alignItems: 'center', + paddingRight: 12 + }, + button: { + marginBottom: 1 + }, + itemWrapper: { + display: 'flex', + flexDirection: 'column', + marginTop: 16, + minHeight: 35 + }, + label: { + extend: label1, + color: offColor, + marginBottom: 4 + }, + item: { + extend: p, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + } +} diff --git a/new-lamassu-admin/src/components/expandable-table/ExpTable.js b/new-lamassu-admin/src/components/tables/DataTable.js similarity index 52% rename from new-lamassu-admin/src/components/expandable-table/ExpTable.js rename to new-lamassu-admin/src/components/tables/DataTable.js index de996cb6..e1608f34 100644 --- a/new-lamassu-admin/src/components/expandable-table/ExpTable.js +++ b/new-lamassu-admin/src/components/tables/DataTable.js @@ -1,5 +1,6 @@ import { makeStyles } from '@material-ui/core/styles' import classnames from 'classnames' +import * as R from 'ramda' import React, { useState } from 'react' import { AutoSizer, @@ -8,33 +9,31 @@ import { CellMeasurerCache } from 'react-virtualized' -import { THead, Tr, Td, Th } from 'src/components/fake-table/Table' +import { + Table, + TBody, + THead, + Tr, + Td, + Th +} from 'src/components/fake-table/Table' import { ReactComponent as ExpandClosedIcon } from 'src/styling/icons/action/expand/closed.svg' import { ReactComponent as ExpandOpenIcon } from 'src/styling/icons/action/expand/open.svg' -import { mainWidth } from 'src/styling/variables' -const styles = { - expandButton: { - border: 'none', - backgroundColor: 'transparent', - cursor: 'pointer', - padding: 4 - }, - row: { - borderRadius: 0 - } -} +import styles from './DataTable.styles' const useStyles = makeStyles(styles) -const ExpRow = ({ +const Row = ({ id, elements, data, + width, Details, expanded, expandRow, - ...props + expWidth, + expandable }) => { const classes = useStyles() @@ -44,34 +43,25 @@ const ExpRow = ({ className={classnames(classes.row)} error={data.error} errorMessage={data.errorMessage}> - {elements - .slice(0, -1) - .map( - ( - { width, className, textAlign, view = it => it?.toString() }, - idx - ) => ( - - {view(data)} - - ) - )} - - - + {elements.map(({ view = it => it?.toString(), ...props }, idx) => ( + + {view(data)} + + ))} + {expandable && ( + + + + )} - {expanded && ( + {expandable && expanded && ( - +
@@ -80,18 +70,22 @@ const ExpRow = ({ ) } -/* rows = [{ columns = [{ name, value, className, textAlign, width }], details, className, error, errorMessage }] - * Don't forget to include the width of the last (expand button) column! - */ -const ExpTable = ({ +const DataTable = ({ elements = [], data = [], Details, className, + expandable, ...props }) => { const [expanded, setExpanded] = useState(null) + const coreWidth = R.compose(R.sum, R.map(R.prop('width')))(elements) + const expWidth = 1200 - coreWidth + const width = coreWidth + (expandable ? expWidth : 0) + + const classes = useStyles({ width }) + const expandRow = id => { setExpanded(id === expanded ? null : id) } @@ -101,7 +95,7 @@ const ExpTable = ({ fixedWidth: true }) - function rowRenderer({ index, isScrolling, key, parent, style }) { + function rowRenderer({ index, key, parent, style }) { return (
-
@@ -124,27 +121,28 @@ const ExpTable = ({ } return ( - <> -
- - {elements.map(({ width, className, textAlign, header }, idx) => ( - - {header} - - ))} - -
-
+ + + {elements.map(({ width, className, textAlign, header }, idx) => ( + + ))} + {expandable && } + + {({ height }) => ( )} - - + +
+ {header} +
) } -export default ExpTable +export default DataTable diff --git a/new-lamassu-admin/src/components/tables/DataTable.styles.js b/new-lamassu-admin/src/components/tables/DataTable.styles.js new file mode 100644 index 00000000..dc0e3e58 --- /dev/null +++ b/new-lamassu-admin/src/components/tables/DataTable.styles.js @@ -0,0 +1,20 @@ +export default { + expandButton: { + border: 'none', + backgroundColor: 'transparent', + cursor: 'pointer', + padding: 4 + }, + row: { + borderRadius: 0 + }, + body: { + flex: [[1, 1, 'auto']] + }, + table: ({ width }) => ({ + width, + flex: 1, + display: 'flex', + flexDirection: 'column' + }) +} diff --git a/new-lamassu-admin/src/components/tables/Stripes.js b/new-lamassu-admin/src/components/tables/Stripes.js new file mode 100644 index 00000000..59cf8ce6 --- /dev/null +++ b/new-lamassu-admin/src/components/tables/Stripes.js @@ -0,0 +1,12 @@ +import React from 'react' + +import { Td } from 'src/components/fake-table/Table' +import { ReactComponent as StripesSvg } from 'src/styling/icons/stripes.svg' + +const Stripes = ({ width }) => ( + + + +) + +export default Stripes diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js index 84a32d74..4d528210 100644 --- a/new-lamassu-admin/src/pages/Customers/Customers.js +++ b/new-lamassu-admin/src/pages/Customers/Customers.js @@ -7,7 +7,7 @@ import * as R from 'ramda' import React from 'react' import Title from 'src/components/Title' -import { DataTable } from 'src/components/dataTable' +import DataTable from 'src/components/tables/DataTable' import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' diff --git a/new-lamassu-admin/src/pages/Funding.js b/new-lamassu-admin/src/pages/Funding.js index 502bd2cd..2275e2c7 100644 --- a/new-lamassu-admin/src/pages/Funding.js +++ b/new-lamassu-admin/src/pages/Funding.js @@ -7,10 +7,10 @@ import moment from 'moment' import QRCode from 'qrcode.react' import React, { useState } from 'react' -import Sidebar from 'src/components/Sidebar' import TableLabel from 'src/components/TableLabel' import Title from 'src/components/Title' import { Tr, Td, THead, TBody, Table } from 'src/components/fake-table/Table' +import Sidebar from 'src/components/layout/Sidebar' import { H3, Info1, diff --git a/new-lamassu-admin/src/pages/Locales/Locales.js b/new-lamassu-admin/src/pages/Locales/Locales.js index e2e999b9..8ad95a52 100644 --- a/new-lamassu-admin/src/pages/Locales/Locales.js +++ b/new-lamassu-admin/src/pages/Locales/Locales.js @@ -6,7 +6,7 @@ import React from 'react' import { Table as EditableTable } from 'src/components/editableTable' import Section from 'src/components/layout/Section' import TitleSection from 'src/components/layout/TitleSection' -import { fromServer, toServer } from 'src/utils/config' +import { fromNamespace, toNamespace } from 'src/utils/config' import { mainFields, @@ -55,25 +55,27 @@ const Locales = ({ name: SCREEN_KEY }) => { refetchQueries: () => ['getData'] }) - const config = data?.config && fromServer(SCREEN_KEY)(data.config) + const config = data?.config && fromNamespace(SCREEN_KEY)(data.config) const locale = config && !R.isEmpty(config) ? config : localeDefaults const save = it => { - const config = toServer(SCREEN_KEY)(it.locale[0]) + const config = toNamespace(SCREEN_KEY)(it.locale[0]) return saveConfig({ variables: { config } }) } const saveOverrides = it => { - const config = toServer(SCREEN_KEY)(it) + const config = toNamespace(SCREEN_KEY)(it) return saveConfig({ variables: { config } }) } return ( <> -
+
{ elements={mainFields(data)} />
-
+
{ - return it ? R.compose(R.join(', '), R.map(R.path(['code'])))(it) : it -} - const getFields = (getData, names) => { return R.filter(it => R.includes(it.name, names), allFields(getData)) } -const allFields = getData => [ - { - name: 'machine', - width: 200, - size: 'sm', - view: R.path(['name']), - input: Autocomplete, - inputProps: { - options: getData(['machines']), - limit: null, - forceShowValue: true, - getOptionSelected: R.eqProps('machineId') - } - }, - { - name: 'country', - width: 200, - size: 'sm', - view: R.path(['display']), - input: Autocomplete, - inputProps: { - options: getData(['countries']), - getOptionSelected: R.eqProps('display') - } - }, - { - name: 'fiatCurrency', - width: 150, - size: 'sm', - view: R.path(['code']), - input: Autocomplete, - inputProps: { - options: getData(['currencies']), - getOptionSelected: R.eqProps('display') - } - }, - { - name: 'languages', - width: 240, - size: 'sm', - view: displayCodeArray, - input: Autocomplete, - inputProps: { - options: getData(['languages']), - getLabel: R.path(['code']), - getOptionSelected: R.eqProps('code'), - multiple: true - } - }, - { - name: 'cryptoCurrencies', - width: 270, - size: 'sm', - view: displayCodeArray, - input: Autocomplete, - inputProps: { - options: getData(['cryptoCurrencies']), - getLabel: it => R.path(['code'])(it) ?? it, - getOptionSelected: R.eqProps('code'), - multiple: true - } +const allFields = getData => { + const getView = (data, code, compare) => it => { + if (!data) return '' + + return R.compose( + R.prop(code), + R.find(R.propEq(compare ?? 'code', it)) + )(data) } -] + + const displayCodeArray = data => it => { + if (!it) return it + + return R.compose(R.join(', '), R.map(getView(data, 'code')))(it) + } + + const machineData = getData(['machines']) + const countryData = getData(['countries']) + const currencyData = getData(['currencies']) + const languageData = getData(['languages']) + const cryptoData = getData(['cryptoCurrencies']) + + return [ + { + name: 'machine', + width: 200, + size: 'sm', + view: getView(machineData, 'name', 'deviceId'), + input: Autocomplete, + inputProps: { + options: machineData, + valueProp: 'deviceId', + getLabel: R.path(['name']), + limit: null + } + }, + { + name: 'country', + width: 200, + size: 'sm', + view: getView(countryData, 'display'), + input: Autocomplete, + inputProps: { + options: countryData, + valueProp: 'code', + getLabel: R.path(['display']) + } + }, + { + name: 'fiatCurrency', + width: 150, + size: 'sm', + view: getView(currencyData, 'code'), + input: Autocomplete, + inputProps: { + options: currencyData, + valueProp: 'code', + getLabel: R.path(['code']) + } + }, + { + name: 'languages', + width: 240, + size: 'sm', + view: displayCodeArray(languageData), + input: Autocomplete, + inputProps: { + options: languageData, + valueProp: 'code', + getLabel: R.path(['code']), + multiple: true + } + }, + { + name: 'cryptoCurrencies', + width: 270, + size: 'sm', + view: displayCodeArray(cryptoData), + input: Autocomplete, + inputProps: { + options: cryptoData, + valueProp: 'code', + getLabel: R.path(['code']), + multiple: true + } + } + ] +} const mainFields = auxData => { const getData = R.path(R.__, auxData) @@ -98,29 +119,29 @@ const overrides = auxData => { } const LocaleSchema = Yup.object().shape({ - country: Yup.object().required('Required'), - fiatCurrency: Yup.object().required('Required'), + country: Yup.string().required('Required'), + fiatCurrency: Yup.string().required('Required'), languages: Yup.array().required('Required'), cryptoCurrencies: Yup.array().required('Required') }) const OverridesSchema = Yup.object().shape({ - machine: Yup.object().required('Required'), - country: Yup.object().required('Required'), + machine: Yup.string().required('Required'), + country: Yup.string().required('Required'), languages: Yup.array().required('Required'), cryptoCurrencies: Yup.array().required('Required') }) const localeDefaults = { - country: null, - fiatCurrency: null, + country: '', + fiatCurrency: '', languages: [], cryptoCurrencies: [] } const overridesDefaults = { - machine: null, - country: null, + machine: '', + country: '', languages: [], cryptoCurrencies: [] } diff --git a/new-lamassu-admin/src/pages/MachineLogs.js b/new-lamassu-admin/src/pages/MachineLogs.js index 847392f7..1bcd6018 100644 --- a/new-lamassu-admin/src/pages/MachineLogs.js +++ b/new-lamassu-admin/src/pages/MachineLogs.js @@ -6,9 +6,9 @@ import * as R from 'ramda' import React, { useState } from 'react' import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper' -import Sidebar from 'src/components/Sidebar' import Title from 'src/components/Title' import { FeatureButton, SimpleButton } from 'src/components/buttons' +import Sidebar from 'src/components/layout/Sidebar' import { Table, TableHead, diff --git a/new-lamassu-admin/src/pages/Notifications/Notifications.js b/new-lamassu-admin/src/pages/Notifications/Notifications.js index 665149d0..0eeb4cc2 100644 --- a/new-lamassu-admin/src/pages/Notifications/Notifications.js +++ b/new-lamassu-admin/src/pages/Notifications/Notifications.js @@ -4,7 +4,7 @@ import * as R from 'ramda' import React, { useState } from 'react' import TitleSection from 'src/components/layout/TitleSection' -import { fromServer, toServer } from 'src/utils/config' +import { fromNamespace, toNamespace } from 'src/utils/config' import Section from '../../components/layout/Section' @@ -49,19 +49,19 @@ const Notifications = ({ name: SCREEN_KEY }) => { onError: error => setError({ error }) }) - const config = data?.config && fromServer(SCREEN_KEY)(data.config) + const config = data?.config && fromNamespace(SCREEN_KEY)(data.config) const machines = data?.machines const cryptoCurrencies = data?.cryptoCurrencies - // TODO check path when locales is finished - const currency = R.path(['locales_currency'])(data?.config ?? {}) + // TODO improve the way of fetching this + const currency = R.path(['locale_fiatCurrency', 'code'])(data?.config ?? {}) - const save = (section, rawConfig) => { - const config = toServer(SCREEN_KEY)(rawConfig) + const save = R.curry((section, rawConfig) => { + const config = toNamespace(SCREEN_KEY)(rawConfig) setSection(section) setError(null) return saveConfig({ variables: { config } }) - } + }) const setEditing = (key, state) => { if (!state) { diff --git a/new-lamassu-admin/src/pages/Notifications/components/EditHeader.js b/new-lamassu-admin/src/pages/Notifications/components/EditHeader.js index 43504ffa..f293dfbf 100644 --- a/new-lamassu-admin/src/pages/Notifications/components/EditHeader.js +++ b/new-lamassu-admin/src/pages/Notifications/components/EditHeader.js @@ -1,7 +1,7 @@ import { makeStyles } from '@material-ui/core' import React from 'react' -import { Link } from 'src/components/buttons' +import { Link, IconButton } from 'src/components/buttons' import { H4 } from 'src/components/typography' import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg' import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' @@ -17,12 +17,12 @@ const Header = ({ title, editing, disabled, setEditing }) => {

{title}

{!editing && ( - + )} {editing && (
diff --git a/new-lamassu-admin/src/pages/Notifications/sections/CryptoBalanceOverrides.js b/new-lamassu-admin/src/pages/Notifications/sections/CryptoBalanceOverrides.js index 203ef66d..fc39f25c 100644 --- a/new-lamassu-admin/src/pages/Notifications/sections/CryptoBalanceOverrides.js +++ b/new-lamassu-admin/src/pages/Notifications/sections/CryptoBalanceOverrides.js @@ -15,7 +15,7 @@ const NAME = 'cryptoBalanceOverrides' const CryptoBalanceOverrides = ({ section }) => { const { - cryptoCurrencies, + cryptoCurrencies = [], data, save, currency, @@ -32,12 +32,17 @@ const CryptoBalanceOverrides = ({ section }) => { return save(newOverrides) } - const getSuggestions = () => { - const overridenCryptos = R.map( - override => override[CRYPTOCURRENCY_KEY], - setupValues + const overridenCryptos = R.map(R.prop(CRYPTOCURRENCY_KEY))(setupValues) + const suggestionFilter = R.filter( + it => !R.contains(it.code, overridenCryptos) + ) + const suggestions = suggestionFilter(cryptoCurrencies) + + const findSuggestion = it => { + const coin = R.compose(R.find(R.propEq('code', it?.cryptoCurrency)))( + cryptoCurrencies ) - return R.without(overridenCryptos, cryptoCurrencies ?? []) + return coin ? [coin] : [] } const initialValues = { @@ -60,7 +65,11 @@ const CryptoBalanceOverrides = ({ section }) => { .required() }) - const suggestions = getSuggestions() + const viewCrypto = it => + R.compose( + R.path(['display']), + R.find(R.propEq('code', it)) + )(cryptoCurrencies) const elements = [ { @@ -68,13 +77,13 @@ const CryptoBalanceOverrides = ({ section }) => { header: 'Cryptocurrency', width: 166, size: 'sm', - view: R.path(['display']), + view: viewCrypto, input: Autocomplete, inputProps: { - options: suggestions, + options: it => R.concat(suggestions, findSuggestion(it)), limit: null, - forceShowValue: true, - getOptionSelected: R.eqProps('display') + valueProp: 'code', + getLabel: R.path(['display']) } }, { diff --git a/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js b/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js index 5a432a56..0e7a8f56 100644 --- a/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js +++ b/new-lamassu-admin/src/pages/Notifications/sections/FiatBalanceOverrides.js @@ -14,16 +14,22 @@ const MACHINE_KEY = 'machine' const NAME = 'fiatBalanceOverrides' const FiatBalanceOverrides = ({ section }) => { - const { machines, data, save, isDisabled, setEditing } = useContext( + const { machines = [], data, save, isDisabled, setEditing } = useContext( NotificationsCtx ) const setupValues = data?.fiatBalanceOverrides ?? [] const innerSetEditing = it => setEditing(NAME, it) - const getSuggestions = () => { - const overridenMachines = R.map(override => override.machine, setupValues) - return R.without(overridenMachines, machines ?? []) + const overridenMachines = R.map(override => override.machine, setupValues) + const suggestionFilter = R.filter( + it => !R.contains(it.code, overridenMachines) + ) + const suggestions = suggestionFilter(machines) + + const findSuggestion = it => { + const coin = R.compose(R.find(R.propEq('deviceId', it?.machine)))(machines) + return coin ? [coin] : [] } const initialValues = { @@ -44,8 +50,6 @@ const FiatBalanceOverrides = ({ section }) => { .required() }) - const suggestions = getSuggestions() - const elements = [ { name: MACHINE_KEY, @@ -54,9 +58,8 @@ const FiatBalanceOverrides = ({ section }) => { view: R.path(['name']), input: Autocomplete, inputProps: { - options: suggestions, + options: it => R.concat(suggestions, findSuggestion(it)), limit: null, - forceShowValue: true, getOptionSelected: R.eqProps('display') } }, diff --git a/new-lamassu-admin/src/pages/Notifications/sections/Setup.js b/new-lamassu-admin/src/pages/Notifications/sections/Setup.js index 9b6b0dbe..7c405fd7 100644 --- a/new-lamassu-admin/src/pages/Notifications/sections/Setup.js +++ b/new-lamassu-admin/src/pages/Notifications/sections/Setup.js @@ -11,6 +11,7 @@ import { Th } from 'src/components/fake-table/Table' import { Switch } from 'src/components/inputs' +import { fromNamespace, toNamespace } from 'src/utils/config' import { startCase } from 'src/utils/string' import NotificationsCtx from '../NotificationsContext' @@ -26,12 +27,15 @@ const sizes = { const width = R.sum(R.values(sizes)) + channelSize const Row = ({ namespace }) => { - const { data, save } = useContext(NotificationsCtx) - const disabled = !data || !data[`${namespace}_active`] + const { data: rawData, save: rawSave } = useContext(NotificationsCtx) + + const save = R.compose(rawSave(null), toNamespace(namespace)) + const data = fromNamespace(namespace)(rawData) + + const disabled = !data || !data.active const Cell = ({ name, disabled }) => { - const namespaced = `${namespace}_${name}` - const value = !!(data && data[namespaced]) + const value = !!(data && data[name]) return ( @@ -39,7 +43,7 @@ const Row = ({ namespace }) => { disabled={disabled} checked={value} onChange={event => { - save(null, { [namespaced]: event.target.checked }) + save({ [name]: event.target.checked }) }} value={value} /> diff --git a/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js index 02bffbc1..ae064e7f 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/CoinATMRadar/CoinATMRadar.js @@ -1,13 +1,13 @@ -import { makeStyles } from '@material-ui/core/styles' -import React, { useState, memo } from 'react' import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core/styles' import { gql } from 'apollo-boost' +import React, { useState, memo } from 'react' -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 { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable' +import { Button } from 'src/components/buttons' import { Switch } from 'src/components/inputs' +import { H4, P, Label2 } from 'src/components/typography' import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg' import { mainStyles } from './CoinATMRadar.styles' diff --git a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js index d930851a..23fe645b 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js @@ -2,8 +2,8 @@ import { makeStyles } from '@material-ui/core' import * as R from 'ramda' import React, { useState } from 'react' -import Sidebar from 'src/components/Sidebar' import Title from 'src/components/Title' +import Sidebar from 'src/components/layout/Sidebar' import logsStyles from '../Logs.styles' diff --git a/new-lamassu-admin/src/pages/Services/Bitgo.js b/new-lamassu-admin/src/pages/Services/Bitgo.js deleted file mode 100644 index 133d5705..00000000 --- a/new-lamassu-admin/src/pages/Services/Bitgo.js +++ /dev/null @@ -1,257 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import TextInputFormik from 'src/components/inputs/formik/TextInput' -import SecretInputFormik from 'src/components/inputs/formik/SecretInput' - -import { Card, getValue as getValueAux, formatLong } from './aux' -import EditService from './EditService' - -const schema = { - token: { - code: 'token', - display: 'API Token' - }, - btcWalletId: { - code: 'BTCWalletId', - display: 'BTC Wallet ID' - }, - btcWalletPassphrase: { - code: 'BTCWalletPassphrase', - display: 'BTC Wallet Passphrase' - }, - ltcWalletId: { - code: 'LTCWalletId', - display: 'LTC Wallet ID' - }, - ltcWalletPassphrase: { - code: 'LTCWalletPassphrase', - display: 'LTC Wallet Passphrase' - }, - zecWalletId: { - code: 'ZECWalletId', - display: 'ZEC Wallet ID' - }, - zecWalletPassphrase: { - code: 'ZECWalletPassphrase', - display: 'ZEC Wallet Passphrase' - }, - bchWalletId: { - code: 'BCHWalletId', - display: 'BCH Wallet ID' - }, - bchWalletPassphrase: { - code: 'BCHWalletPassphrase', - display: 'BCH Wallet Passphrase' - }, - dashWalletId: { - code: 'DASHWalletId', - display: 'DASH Wallet ID' - }, - dashWalletPassphrase: { - code: 'DASHWalletPassphrase', - display: 'DASH Wallet Passphrase' - }, - environment: { - code: 'environment', - display: 'Environment' - } -} - -const BitgoCard = memo(({ account, onEdit, ...props }) => { - const getValue = getValueAux(account) - - const token = schema.token - const tokenValue = getValue(token.code) - - const items = [ - { - label: token.display, - value: formatLong(tokenValue) - } - ] - - return ( - - ) -}) - -const getBitgoFormik = account => { - const getValue = getValueAux(account) - - const token = getValue(schema.token.code) - const btcWalletId = getValue(schema.btcWalletId.code) - const btcWalletPassphrase = getValue(schema.btcWalletPassphrase.code) - const ltcWalletId = getValue(schema.ltcWalletId.code) - const ltcWalletPassphrase = getValue(schema.ltcWalletPassphrase.code) - const zecWalletId = getValue(schema.zecWalletId.code) - const zecWalletPassphrase = getValue(schema.zecWalletPassphrase.code) - const bchWalletId = getValue(schema.bchWalletId.code) - const bchWalletPassphrase = getValue(schema.bchWalletPassphrase.code) - const dashWalletId = getValue(schema.dashWalletId.code) - const dashWalletPassphrase = getValue(schema.dashWalletPassphrase.code) - const environment = getValue(schema.environment.code) - - return { - initialValues: { - token: token, - BTCWalletId: btcWalletId, - BTCWalletPassphrase: btcWalletPassphrase, - LTCWalletId: ltcWalletId, - LTCWalletPassphrase: ltcWalletPassphrase, - ZECWalletId: zecWalletId, - ZECWalletPassphrase: zecWalletPassphrase, - BCHWalletId: bchWalletId, - BCHWalletPassphrase: bchWalletPassphrase, - DASHWalletId: dashWalletId, - DASHWalletPassphrase: dashWalletPassphrase, - environment: environment - }, - validationSchema: Yup.object().shape({ - token: Yup.string() - .max(100, 'Too long') - .required('Required'), - btcWalletId: Yup.string().max(100, 'Too long'), - btcWalletPassphrase: Yup.string().max(100, 'Too long'), - ltcWalletId: Yup.string().max(100, 'Too long'), - ltcWalletPassphrase: Yup.string().max(100, 'Too long'), - zecWalletId: Yup.string().max(100, 'Too long'), - zecWalletPassphrase: Yup.string().max(100, 'Too long'), - bchWalletId: Yup.string().max(100, 'Too long'), - bchWalletPassphrase: Yup.string().max(100, 'Too long'), - dashWalletId: Yup.string().max(100, 'Too long'), - dashWalletPassphrase: Yup.string().max(100, 'Too long'), - environment: Yup.string() - .matches(/(prod|test)/) - .required('Required') - }), - validate: values => { - const errors = {} - - if (values.btcWalletId && !values.btcWalletPassphrase) { - errors.btcWalletPassphrase = 'Required' - } - - if (values.ltcWalletId && !values.ltcWalletPassphrase) { - errors.ltcWalletPassphrase = 'Required' - } - - if (values.zecWalletId && !values.zecWalletPassphrase) { - errors.zecWalletPassphrase = 'Required' - } - - if (values.bchWalletId && !values.bchWalletPassphrase) { - errors.bchWalletPassphrase = 'Required' - } - - if (values.dashWalletId && !values.dashWalletPassphrase) { - errors.dashWalletPassphrase = 'Required' - } - - return errors - } - } -} - -const getBitgoFields = () => [ - { - name: schema.token.code, - label: schema.token.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.btcWalletId.code, - label: schema.btcWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.btcWalletPassphrase.code, - label: schema.btcWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.ltcWalletId.code, - label: schema.ltcWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.ltcWalletPassphrase.code, - label: schema.ltcWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.zecWalletId.code, - label: schema.zecWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.zecWalletPassphrase.code, - label: schema.zecWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.bchWalletId.code, - label: schema.bchWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.bchWalletPassphrase.code, - label: schema.bchWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.dashWalletId.code, - label: schema.dashWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.dashWalletPassphrase.code, - label: schema.dashWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.environment.code, - label: schema.environment.display, - placeholder: 'prod or test', - type: 'text', - component: TextInputFormik - } -] - -const BitgoForm = ({ account, handleSubmit, ...props }) => { - const { code } = account - - const formik = getBitgoFormik(account) - - const fields = getBitgoFields() - - return ( - <> - - - ) -} - -export { BitgoForm, BitgoCard, getBitgoFormik, getBitgoFields } diff --git a/new-lamassu-admin/src/pages/Services/Bitstamp.js b/new-lamassu-admin/src/pages/Services/Bitstamp.js deleted file mode 100644 index 8216f632..00000000 --- a/new-lamassu-admin/src/pages/Services/Bitstamp.js +++ /dev/null @@ -1,123 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import TextInputFormik from 'src/components/inputs/formik/TextInput' -import SecretInputFormik from 'src/components/inputs/formik/SecretInput' - -import { Card, getValue as getValueAux, formatLong } from './aux' -import EditService from './EditService' - -const schema = { - clientId: { - code: 'clientId', - display: 'Client ID' - }, - key: { - code: 'key', - display: 'API Key' - }, - secret: { - code: 'secret', - display: 'API Secret' - } -} - -const BitstampCard = memo(({ account, onEdit, ...props }) => { - const findValue = getValueAux(account) - - const clientId = schema.clientId - const key = schema.key - - const clientIdValue = findValue(clientId.code) - const keyValue = findValue(key.code) - - const items = [ - { - label: clientId.display, - value: formatLong(clientIdValue) - }, - { - label: key.display, - value: formatLong(keyValue) - } - ] - - return ( - - ) -}) - -const getBitstampFormik = account => { - const getValue = getValueAux(account) - - const clientId = getValue(schema.clientId.code) - const key = getValue(schema.key.code) - const secret = getValue(schema.secret.code) - - return { - initialValues: { - clientId: clientId, - key: key, - secret: secret - }, - validationSchema: Yup.object().shape({ - clientId: Yup.string() - .max(100, 'Too long') - .required('Required'), - key: Yup.string() - .max(100, 'Too long') - .required('Required'), - secret: Yup.string() - .max(100, 'Too long') - .required('Required') - }) - } -} - -const getBitstampFields = () => [ - { - name: schema.clientId.code, - label: schema.clientId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.key.code, - label: schema.key.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.secret.code, - label: schema.secret.display, - type: 'text', - component: SecretInputFormik - } -] - -const BitstampForm = ({ account, ...props }) => { - const { code } = account - - const formik = getBitstampFormik(account) - - const fields = getBitstampFields() - - return ( - <> - - - ) -} - -export { BitstampForm, BitstampCard, getBitstampFormik, getBitstampFields } diff --git a/new-lamassu-admin/src/pages/Services/Blockcypher.js b/new-lamassu-admin/src/pages/Services/Blockcypher.js deleted file mode 100644 index cb3ad18d..00000000 --- a/new-lamassu-admin/src/pages/Services/Blockcypher.js +++ /dev/null @@ -1,113 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import TextInputFormik from 'src/components/inputs/formik/TextInput' - -import { Card, getValue as getValueAux, formatLong } from './aux' -import EditService from './EditService' - -const schema = { - token: { - code: 'token', - display: 'API Token' - }, - confidenceFactor: { - code: 'confidenceFactor', - display: 'Confidence Factor' - } -} - -const BlockcypherCard = memo(({ account, onEdit, ...props }) => { - const getValue = getValueAux(account) - - const token = schema.token - const confidenceFactor = schema.confidenceFactor - - const tokenValue = getValue(token.code) - const confidenceFactorValue = getValue(confidenceFactor.code) - - const items = [ - { - label: token.display, - value: formatLong(tokenValue) - }, - { - label: confidenceFactor.display, - value: confidenceFactorValue - } - ] - - return ( - - ) -}) - -const getBlockcypherFormik = account => { - const getValue = getValueAux(account) - - const token = getValue(schema.token.code) - const confidenceFactor = getValue(schema.confidenceFactor.code) - - return { - initialValues: { - token: token, - confidenceFactor: confidenceFactor - }, - validationSchema: Yup.object().shape({ - token: Yup.string() - .max(100, 'Too long') - .required('Required'), - confidenceFactor: Yup.number() - .integer('Please input a positive integer') - .positive('Please input a positive integer') - .required('Required') - }) - } -} - -const getBlockcypherFields = () => [ - { - name: schema.token.code, - label: schema.token.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.confidenceFactor.code, - label: schema.confidenceFactor.display, - type: 'text', - component: TextInputFormik - } -] - -const BlockcypherForm = ({ account, ...props }) => { - const { code } = account - - const formik = getBlockcypherFormik(account) - - const fields = getBlockcypherFields() - - return ( - <> - - - ) -} - -export { - BlockcypherForm, - BlockcypherCard, - getBlockcypherFormik, - getBlockcypherFields -} diff --git a/new-lamassu-admin/src/pages/Services/EditService.js b/new-lamassu-admin/src/pages/Services/EditService.js deleted file mode 100644 index 32940cce..00000000 --- a/new-lamassu-admin/src/pages/Services/EditService.js +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useState } from 'react' -import { Form, Formik, Field } from 'formik' -import classnames from 'classnames' -import { makeStyles, Paper } from '@material-ui/core' - -import { H2, Info3 } from 'src/components/typography' -import { Button } from 'src/components/buttons' -import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg' -import { ReactComponent as ErrorIcon } from 'src/styling/icons/warning-icon/tomato.svg' - -import { editServiceStyles as styles } from './Services.styles' - -const useStyles = makeStyles(styles) - -const DEFAULT_ERROR_MESSAGE = 'Something went wrong. Please contact support.' - -const EditService = ({ - title, - code, - formik, - fields, - handleClose, - save, - ...props -}) => { - const [error, setError] = useState(props.error) - - const classes = useStyles() - - const submitWrapperClasses = { - [classes.submitWrapper]: true, - [classes.submitError]: error - } - - return ( - - -
-

{`Edit ${title}`}

-
-
- { - save(code, values) - .then(m => handleClose()) - .catch(err => { - if (err) setError(true) - }) - }}> -
-
- {fields && - fields.map((field, idx) => ( -
- { - setError(null) - }} - /> -
- ))} -
-
-
- {error && ( -
- - - {DEFAULT_ERROR_MESSAGE} - -
- )} - -
-
-
-
-
-
- ) -} - -export default EditService diff --git a/new-lamassu-admin/src/pages/Services/FormRenderer.js b/new-lamassu-admin/src/pages/Services/FormRenderer.js new file mode 100644 index 00000000..acc80047 --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/FormRenderer.js @@ -0,0 +1,67 @@ +import { makeStyles, Grid } from '@material-ui/core' +import { Formik, Form, FastField } from 'formik' +import * as R from 'ramda' +import React from 'react' + +import { Button } from 'src/components/buttons' + +const styles = { + button: { + margin: [['auto', 0, 32, 'auto']] + }, + form: { + flex: 1, + display: 'flex', + flexDirection: 'column' + }, + grid: { + marginBottom: 24, + marginTop: 12 + } +} + +const useStyles = makeStyles(styles) +const FormRenderer = ({ + validationSchema, + elements, + value, + save, + buttonLabel = 'Save changes' +}) => { + const classes = useStyles() + + const initialValues = R.compose( + R.mergeAll, + R.map(({ code }) => ({ [code]: (value && value[code]) ?? '' })) + )(elements) + + const values = R.merge(initialValues, value) + + return ( + +
+ + {elements.map(({ component, code, display }) => ( + + + + ))} + + +
+
+ ) +} + +export default FormRenderer diff --git a/new-lamassu-admin/src/pages/Services/Infura.js b/new-lamassu-admin/src/pages/Services/Infura.js deleted file mode 100644 index 47e3aa9b..00000000 --- a/new-lamassu-admin/src/pages/Services/Infura.js +++ /dev/null @@ -1,126 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import TextInputFormik from 'src/components/inputs/formik/TextInput' -import SecretInputFormik from 'src/components/inputs/formik/SecretInput' - -import { Card, getValue as getValueAux, formatLong } from './aux' -import EditService from './EditService' - -const schema = { - apiKey: { - code: 'apiKey', - display: 'API Key' - }, - apiSecret: { - code: 'apiSecret', - display: 'API Secret' - }, - endpoint: { - code: 'endpoint', - display: 'Endpoint' - } -} - -const InfuraCard = memo(({ account, onEdit, ...props }) => { - const getValue = getValueAux(account) - - const apiKey = schema.apiKey - const apiSecret = schema.apiSecret - - const apiKeyValue = getValue(apiKey.code) - const apiSecretValue = getValue(apiSecret.code) - - const items = [ - { - label: apiKey.display, - value: formatLong(apiKeyValue) - }, - { - label: apiSecret.display, - value: formatLong(apiSecretValue) - } - ] - - return ( - - ) -}) - -const getInfuraFormik = account => { - const getValue = getValueAux(account) - - const apiKey = getValue(schema.apiKey.code) - const apiSecret = getValue(schema.apiSecret.code) - const endpoint = getValue(schema.endpoint.code) - - return { - initialValues: { - apiKey: apiKey, - apiSecret: apiSecret, - endpoint: endpoint - }, - validationSchema: Yup.object().shape({ - apiKey: Yup.string() - .max(100, 'Too long') - .required('Required'), - apiSecret: Yup.string() - .max(100, 'Too long') - .required('Required'), - endpoint: Yup.string() - .max(100, 'Too long') - .url('Please input a valid url') - .required('Required') - }) - } -} - -const getInfuraFields = () => { - return [ - { - name: schema.apiKey.code, - label: schema.apiKey.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.apiSecret.code, - label: schema.apiSecret.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.endpoint.code, - label: schema.endpoint.display, - type: 'text', - component: TextInputFormik - } - ] -} - -const InfuraForm = ({ account, ...props }) => { - const { code } = account - - const formik = getInfuraFormik(account) - - const fields = getInfuraFields() - - return ( - <> - - - ) -} - -export { InfuraCard, InfuraForm, getInfuraFormik, getInfuraFields } diff --git a/new-lamassu-admin/src/pages/Services/Itbit.js b/new-lamassu-admin/src/pages/Services/Itbit.js deleted file mode 100644 index 23371a4e..00000000 --- a/new-lamassu-admin/src/pages/Services/Itbit.js +++ /dev/null @@ -1,133 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import TextInputFormik from 'src/components/inputs/formik/TextInput' -import SecretInputFormik from 'src/components/inputs/formik/SecretInput' - -import { Card, getValue as getValueAux, formatLong } from './aux' -import EditService from './EditService' - -const schema = { - userId: { - code: 'userId', - display: 'User ID' - }, - walletId: { - code: 'walletId', - display: 'Wallet ID' - }, - clientKey: { - code: 'clientKey', - display: 'Client Key' - }, - clientSecret: { - code: 'clientSecret', - display: 'Client Secret' - } -} - -const ItbitCard = memo(({ account, onEdit, ...props }) => { - const getValue = getValueAux(account) - - const userId = schema.userId - const walletId = schema.walletId - - const userIdValue = getValue(userId.code) - const walletIdValue = getValue(walletId.code) - - const items = [ - { - label: userId.display, - value: formatLong(userIdValue) - }, - { - label: walletId.display, - value: formatLong(walletIdValue) - } - ] - - return ( - - ) -}) - -const getItbitFormik = account => { - const getValue = getValueAux(account) - - const userId = getValue(schema.userId.code) - const walletId = getValue(schema.walletId.code) - const clientKey = getValue(schema.clientKey.code) - const clientSecret = getValue(schema.clientSecret.code) - - return { - initialValues: { - userId: userId, - walletId: walletId, - clientKey: clientKey, - clientSecret: clientSecret - }, - validationSchema: Yup.object().shape({ - userId: Yup.string() - .max(100, 'Too long') - .required('Required'), - walletId: Yup.string() - .max(100, 'Too long') - .required('Required'), - clientKey: Yup.string() - .max(100, 'Too long') - .required('Required'), - clientSecret: Yup.string() - .max(100, 'Too long') - .required('Required') - }) - } -} - -const getItbitFields = () => [ - { - name: schema.userId.code, - label: schema.userId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.walletId.code, - label: schema.walletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.clientKey.code, - label: schema.clientKey.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.clientSecret.code, - label: schema.clientSecret.display, - type: 'text', - component: SecretInputFormik - } -] - -const ItbitForm = ({ account, ...props }) => { - const { code } = account - - const formik = getItbitFormik(account) - - const fields = getItbitFields() - - return ( - <> - - - ) -} - -export { ItbitCard, ItbitForm, getItbitFormik, getItbitFields } diff --git a/new-lamassu-admin/src/pages/Services/Kraken.js b/new-lamassu-admin/src/pages/Services/Kraken.js deleted file mode 100644 index 7587d849..00000000 --- a/new-lamassu-admin/src/pages/Services/Kraken.js +++ /dev/null @@ -1,108 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import TextInputFormik from 'src/components/inputs/formik/TextInput' -import SecretInputFormik from 'src/components/inputs/formik/SecretInput' - -import { Card, getValue as getValueAux, formatLong } from './aux' -import EditService from './EditService' - -const schema = { - apiKey: { - code: 'apiKey', - display: 'API Key' - }, - privateKey: { - code: 'privateKey', - display: 'Private Key' - } -} - -const KrakenCard = memo(({ account, onEdit, ...props }) => { - const getValue = getValueAux(account) - - const apiKey = schema.apiKey - const privateKey = schema.privateKey - - const apiKeyValue = getValue(apiKey.code) - const privateKeyValue = getValue(privateKey.code) - - const items = [ - apiKey && { - label: apiKey.display, - value: formatLong(apiKeyValue) - }, - privateKey && { - label: privateKey.display, - value: formatLong(privateKeyValue) - } - ] - - return ( - - ) -}) - -const getKrakenFormik = account => { - const getValue = getValueAux(account) - - const apiKey = getValue(schema.apiKey.code) - const privateKey = getValue(schema.privateKey.code) - - return { - initialValues: { - apiKey: apiKey, - privateKey: privateKey - }, - validationSchema: Yup.object().shape({ - apiKey: Yup.string() - .max(100, 'Too long') - .required('Required'), - privateKey: Yup.string() - .max(100, 'Too long') - .required('Required') - }) - } -} - -const getKrakenFields = () => [ - { - name: schema.apiKey.code, - label: schema.apiKey.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.privateKey.code, - label: schema.privateKey.display, - type: 'text', - component: SecretInputFormik - } -] - -const KrakenForm = ({ account, ...props }) => { - const { code } = account - - const formik = getKrakenFormik(account) - - const fields = getKrakenFields() - - return ( - <> - - - ) -} - -export { KrakenCard, KrakenForm, getKrakenFormik, getKrakenFields } diff --git a/new-lamassu-admin/src/pages/Services/Mailgun.js b/new-lamassu-admin/src/pages/Services/Mailgun.js deleted file mode 100644 index 1347e43f..00000000 --- a/new-lamassu-admin/src/pages/Services/Mailgun.js +++ /dev/null @@ -1,139 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import TextInputFormik from 'src/components/inputs/formik/TextInput' - -import { Card, getValue as getValueAux } from './aux' -import EditService from './EditService' - -const schema = { - apiKey: { - code: 'apiKey', - display: 'API Key' - }, - domain: { - code: 'domain', - display: 'Domain' - }, - fromEmail: { - code: 'fromEmail', - display: 'From Email' - }, - toEmail: { - code: 'toEmail', - display: 'To Email' - } -} - -const MailgunCard = memo(({ account, onEdit, ...props }) => { - const getValue = getValueAux(account) - - const fromEmail = schema.fromEmail - const toEmail = schema.toEmail - - const fromEmailValue = getValue(fromEmail.code) - const toEmailValue = getValue(toEmail.code) - - const items = [ - { - label: fromEmail.display, - value: fromEmailValue - }, - { - label: toEmail.display, - value: toEmailValue - } - ] - - return ( - - ) -}) - -const getMailgunFormik = account => { - const getValue = getValueAux(account) - - const apiKey = getValue(schema.apiKey.code) - const domain = getValue(schema.domain.code) - const fromEmail = getValue(schema.fromEmail.code) - const toEmail = getValue(schema.toEmail.code) - - return { - initialValues: { - apiKey: apiKey, - domain: domain, - fromEmail: fromEmail, - toEmail: toEmail - }, - validationSchema: Yup.object().shape({ - apiKey: Yup.string() - .max(100, 'Too long') - .required('Required'), - domain: Yup.string() - .max(100, 'Too long') - .required('Required'), - fromEmail: Yup.string() - .max(100, 'Too long') - .email('Please input a valid email address') - .required('Required'), - toEmail: Yup.string() - .max(100, 'Too long') - .email('Please input a valid email address') - .required('Required') - }) - } -} - -const getMailgunFields = () => [ - { - name: schema.apiKey.code, - label: schema.apiKey.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.domain.code, - label: schema.domain.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.fromEmail.code, - label: schema.fromEmail.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.toEmail.code, - label: schema.toEmail.display, - type: 'text', - component: TextInputFormik - } -] - -const MailgunForm = ({ account, ...props }) => { - const { code } = account - - const formik = getMailgunFormik(account) - - const fields = getMailgunFields() - - return ( - <> - - - ) -} - -export { MailgunCard, MailgunForm, getMailgunFormik, getMailgunFields } diff --git a/new-lamassu-admin/src/pages/Services/Services.js b/new-lamassu-admin/src/pages/Services/Services.js index 4e5fb414..62c5af9b 100644 --- a/new-lamassu-admin/src/pages/Services/Services.js +++ b/new-lamassu-admin/src/pages/Services/Services.js @@ -1,241 +1,96 @@ -import React, { useState } from 'react' -import * as R from 'ramda' -import { gql } from 'apollo-boost' -import { makeStyles, Modal } from '@material-ui/core' import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles, Grid } from '@material-ui/core' +import { gql } from 'apollo-boost' +import * as R from 'ramda' +import React, { useState } from 'react' -import Title from 'src/components/Title' +import Modal from 'src/components/Modal' +import TitleSection from 'src/components/layout/TitleSection' +import SingleRowTable from 'src/components/single-row-table/SingleRowTable' +import { formatLong } from 'src/utils/string' -import { BitgoCard, BitgoForm } from './Bitgo' -import { BitstampCard, BitstampForm } from './Bitstamp' -import { BlockcypherCard, BlockcypherForm } from './Blockcypher' -import { InfuraCard, InfuraForm } from './Infura' -import { ItbitCard, ItbitForm } from './Itbit' -import { KrakenCard, KrakenForm } from './Kraken' -import { MailgunCard, MailgunForm } from './Mailgun' -import { StrikeCard, StrikeForm } from './Strike' -import { TwilioCard, TwilioForm } from './Twilio' -import { servicesStyles as styles } from './Services.styles' +import FormRenderer from './FormRenderer' +import schemas from './schemas' + +const GET_INFO = gql` + query getData { + accounts + } +` + +const SAVE_ACCOUNT = gql` + mutation Save($account: JSONObject) { + saveAccount(account: $account) + } +` + +const styles = { + wrapper: { + // widths + spacing is a little over 1200 on the design + // this adjusts the margin after a small reduction on card size + marginLeft: 1 + } +} const useStyles = makeStyles(styles) -const GET_CONFIG = gql` - { - config - } -` +const Services = ({ key: SCREEN_KEY }) => { + const [editingSchema, setEditingSchema] = useState(null) -const GET_ACCOUNTS = gql` - { - accounts { - code - display - class - cryptos - } - } -` - -const SAVE_CONFIG = gql` - mutation Save($config: JSONObject) { - saveConfig(config: $config) - } -` - -const Services = () => { - const [open, setOpen] = useState(false) - const [modalContent, setModalContent] = useState(null) - const [accountsConfig, setAccountsConfig] = useState(null) - const [saveConfig, { loading }] = useMutation(SAVE_CONFIG, { - onCompleted: data => setAccountsConfig(data.saveConfig.accounts) + const { data } = useQuery(GET_INFO) + const [saveAccount] = useMutation(SAVE_ACCOUNT, { + onCompleted: () => setEditingSchema(null), + refetchQueries: ['getData'] }) const classes = useStyles() - useQuery(GET_CONFIG, { - onCompleted: data => setAccountsConfig(data.config.accounts ?? {}) - }) - const { data: accountsResponse } = useQuery(GET_ACCOUNTS) + const accounts = data?.accounts ?? [] - const accounts = accountsResponse?.accounts + const getValue = code => R.find(R.propEq('code', code))(accounts) - const save = (code, it) => { - const newAccounts = R.clone(accountsConfig) - newAccounts[code] = it - return saveConfig({ variables: { config: { accounts: newAccounts } } }) + const getItems = (code, elements) => { + const faceElements = R.filter(R.prop('face'))(elements) + const values = getValue(code) || {} + return R.map(({ display, code, long }) => ({ + label: display, + value: long ? formatLong(values[code]) : values[code] + }))(faceElements) } - const getAccount = code => { - return R.mergeDeepLeft( - R.find(R.propEq('code', code))(accounts) ?? {}, - accountsConfig[code] ?? {} - ) - } - - const handleOpen = content => { - setOpen(true) - setModalContent(content) - } - - const handleClose = (canClose = true) => { - if (canClose && !loading) { - setOpen(false) - setModalContent(null) - } - } - - if (!accounts || !accountsConfig) return null - - const codes = { - bitgo: 'bitgo', - bitstamp: 'bitstamp', - blockcypher: 'blockcypher', - infura: 'infura', - itbit: 'itbit', - kraken: 'kraken', - mailgun: 'mailgun', - strike: 'strike', - twilio: 'twilio' - } - - const bitgo = getAccount(codes.bitgo) - const bitstamp = getAccount(codes.bitstamp) - const blockcypher = getAccount(codes.blockcypher) - const infura = getAccount(codes.infura) - const itbit = getAccount(codes.itbit) - const kraken = getAccount(codes.kraken) - const mailgun = getAccount(codes.mailgun) - const strike = getAccount(codes.strike) - const twilio = getAccount(codes.twilio) - return ( - <> -
-
- 3rd Party Services -
-
-
- - handleOpen( - - ) - } - /> - - handleOpen( - - ) - } - /> - - handleOpen( - - ) - } - /> - - handleOpen( - - ) - } - /> - - handleOpen( - - ) - } - /> - - handleOpen( - - ) - } - /> - - handleOpen( - - ) - } - /> - - handleOpen( - - ) - } - /> - - handleOpen( - - ) - } - /> -
- {modalContent && ( +
+ + + {R.values(schemas).map(schema => ( + + setEditingSchema(schema)} + items={getItems(schema.code, schema.elements)} + /> + + ))} + + {editingSchema && ( -
{modalContent}
+ title={`Edit ${editingSchema.name}`} + width={478} + handleClose={() => setEditingSchema(null)} + open={true}> + + saveAccount({ + variables: { account: { code: editingSchema.code, ...it } } + }) + } + elements={editingSchema.elements} + validationSchema={editingSchema.validationSchema} + value={getValue(editingSchema.code)} + />
)} - +
) } diff --git a/new-lamassu-admin/src/pages/Services/Services.styles.js b/new-lamassu-admin/src/pages/Services/Services.styles.js deleted file mode 100644 index 818ea3d7..00000000 --- a/new-lamassu-admin/src/pages/Services/Services.styles.js +++ /dev/null @@ -1,177 +0,0 @@ -import { white, offColor, errorColor } from 'src/styling/variables' -import typographyStyles from 'src/components/typography/styles' -import baseStyles from 'src/pages/Logs.styles' - -const { titleWrapper } = baseStyles -const { label1, p } = typographyStyles - -const servicesStyles = { - titleWrapper, - titleContainer: { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - width: '100%' - }, - addServiceMenu: { - width: 215, - '& > ul': { - padding: [[18, 16, 21, 16]], - '& > li': { - display: 'flex', - justifyContent: 'space-between', - listStyle: 'none', - marginBottom: 23, - cursor: 'pointer', - '& > span:first-child': { - extend: p, - fontWeight: 'bold' - }, - '& > span:last-child': { - extend: label1, - color: offColor - }, - '&:last-child': { - marginBottom: 0 - } - } - } - }, - mainWrapper: { - display: 'flex', - flexWrap: 'wrap' - }, - modal: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - '& > div': { - outline: 'none' - } - }, - modalHeader: { - display: 'flex', - justifyContent: 'space-between', - '& button': { - border: 'none', - backgroundColor: 'transparent', - cursor: 'pointer' - } - }, - modalBody: { - '& > form': { - display: 'flex', - flexDirection: 'column', - position: 'relative', - minHeight: 400, - '& > div:last-child': { - display: 'flex', - alignItems: 'flex-end', - flex: 'auto', - alignSelf: 'flex-end', - '& > button': { - marginTop: 32 - } - } - } - }, - paper: { - position: 'absolute', - backgroundColor: white, - outline: '0 none', - padding: [[16, 20, 32, 24]] - }, - inputField: { - width: 434 - }, - formLabel: { - extend: label1 - } -} - -const editServiceStyles = { - paper: { - padding: [[5, 20, 32, 24]], - position: 'relative', - display: 'flex', - flexDirection: 'column', - minHeight: 524, - overflow: 'hidden', - '& > button': { - position: 'absolute', - top: 16, - right: 16, - border: 'none', - backgroundColor: 'transparent', - cursor: 'pointer', - '& svg': { - width: 18 - } - }, - '& form': { - display: 'flex', - flexDirection: 'column', - flexGrow: 2 - } - }, - modalHeader: { - display: 'flex', - marginBottom: 14 - }, - modalBody: { - display: 'flex', - flexGrow: 2 - }, - formBody: { - display: 'flex', - flexDirection: 'column' - }, - field: { - position: 'relative', - '& > div': { - width: 434 - } - }, - submitWrapper: { - display: 'flex', - justifyContent: 'flex-end', - alignItems: 'flex-end', - flexGrow: 2, - marginTop: 32, - '& > div': { - display: 'flex', - alignItems: 'center', - width: '100%', - justifyContent: 'flex-end', - '& > button': { - '&:active': { - marginTop: 0 - } - } - } - }, - submitError: { - '& > div': { - justifyContent: 'space-between' - } - }, - messageWrapper: { - '& > div': { - display: 'flex', - alignItems: 'center', - '& > svg': { - marginRight: 10 - } - } - }, - message: { - display: 'flex', - alignItems: 'center', - color: errorColor, - margin: 0, - whiteSpace: 'break-spaces', - width: 250 - } -} - -export { servicesStyles, editServiceStyles } diff --git a/new-lamassu-admin/src/pages/Services/Strike.js b/new-lamassu-admin/src/pages/Services/Strike.js deleted file mode 100644 index e8eef813..00000000 --- a/new-lamassu-admin/src/pages/Services/Strike.js +++ /dev/null @@ -1,86 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import SecretInputFormik from 'src/components/inputs/formik/SecretInput' - -import { Card, getValue as getValueAux, formatLong } from './aux' -import EditService from './EditService' - -const schema = { - token: { - code: 'token', - display: 'API Token' - } -} - -const StrikeCard = memo(({ account, onEdit, ...props }) => { - const getValue = getValueAux(account) - - const token = schema.token - - const tokenValue = getValue(token.code) - - const items = [ - { - label: token.display, - value: formatLong(tokenValue) - } - ] - - return ( - - ) -}) - -const getStrikeFormik = account => { - const getValue = getValueAux(account) - - const token = getValue(schema.token.code) - - return { - initialValues: { - token: token - }, - validationSchema: Yup.object().shape({ - token: Yup.string() - .max(100, 'Too long') - .required('Required') - }) - } -} - -const getStrikeFields = () => [ - { - name: schema.token.code, - label: schema.token.display, - type: 'text', - component: SecretInputFormik - } -] - -const StrikeForm = ({ account, ...props }) => { - const code = 'strike' - - const formik = getStrikeFormik(account) - - const fields = getStrikeFields() - - return ( - <> - - - ) -} - -export { StrikeCard, StrikeForm, getStrikeFormik, getStrikeFields } diff --git a/new-lamassu-admin/src/pages/Services/Twilio.js b/new-lamassu-admin/src/pages/Services/Twilio.js deleted file mode 100644 index 15a6c5ae..00000000 --- a/new-lamassu-admin/src/pages/Services/Twilio.js +++ /dev/null @@ -1,138 +0,0 @@ -import React, { memo } from 'react' -import * as Yup from 'yup' - -import TextInputFormik from 'src/components/inputs/formik/TextInput' -import SecretInputFormik from 'src/components/inputs/formik/SecretInput' - -import { Card, getValue as getValueAux } from './aux' -import EditService from './EditService' - -const schema = { - accountSid: { - code: 'accountSid', - display: 'Account SID' - }, - authToken: { - code: 'authToken', - display: 'Auth Token' - }, - fromNumber: { - code: 'fromNumber', - display: 'From Number' - }, - toNumber: { - code: 'toNumber', - display: 'To Number' - } -} - -const TwilioCard = memo(({ account, onEdit, ...props }) => { - const getValue = getValueAux(account) - - const fromNumber = schema.fromNumber - const toNumber = schema.toNumber - - const fromNumberValue = getValue(fromNumber.code) - const toNumberValue = getValue(toNumber.code) - - const items = [ - { - label: fromNumber.display, - value: fromNumberValue - }, - { - label: toNumber.display, - value: toNumberValue - } - ] - - return ( - - ) -}) - -const getTwilioFormik = account => { - const getValue = getValueAux(account) - - const accountSid = getValue(schema.accountSid.code) - const authToken = getValue(schema.authToken.code) - const fromNumber = getValue(schema.fromNumber.code) - const toNumber = getValue(schema.toNumber.code) - - return { - initialValues: { - accountSid: accountSid, - authToken: authToken, - fromNumber: fromNumber, - toNumber: toNumber - }, - validationSchema: Yup.object().shape({ - accountSid: Yup.string() - .max(100, 'Too long') - .required('Required'), - authToken: Yup.string() - .max(100, 'Too long') - .required('Required'), - fromNumber: Yup.string() - .max(100, 'Too long') - .required('Required'), - toNumber: Yup.string() - .max(100, 'Too long') - .required('Required') - }) - } -} - -const getTwilioFields = () => [ - { - name: schema.accountSid.code, - label: schema.accountSid.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.authToken.code, - label: schema.authToken.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.fromNumber.code, - label: schema.fromNumber.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.toNumber.code, - label: schema.toNumber.display, - type: 'text', - component: TextInputFormik - } -] - -const TwilioForm = ({ account, ...props }) => { - const { code } = account - - const formik = getTwilioFormik(account) - - const fields = getTwilioFields() - - return ( - <> - - - ) -} - -export { TwilioCard, TwilioForm, getTwilioFormik, getTwilioFields } diff --git a/new-lamassu-admin/src/pages/Services/aux.js b/new-lamassu-admin/src/pages/Services/aux.js deleted file mode 100644 index bdb03ddf..00000000 --- a/new-lamassu-admin/src/pages/Services/aux.js +++ /dev/null @@ -1,44 +0,0 @@ -import { makeStyles } from '@material-ui/core' -import * as R from 'ramda' -import React from 'react' - -import SingleRowTable from 'src/components/single-row-table/SingleRowTable' - -const getValue = R.curry((account, code) => (account ? account[code] : '')) - -const formatLong = value => { - if (!value) return '' - if (value.length <= 20) return value - - return `${value.slice(0, 8)}(...)${value.slice( - value.length - 8, - value.length - )}` -} - -const styles = { - card: { - margin: [[0, 30, 32, 0]], - paddingBottom: 24, - '&:nth-child(3n+3)': { - marginRight: 0 - } - } -} - -const useStyles = makeStyles(styles) - -const Card = ({ account, title, items, onEdit, ...props }) => { - const classes = useStyles() - - return ( - - ) -} - -export { Card, getValue, formatLong } diff --git a/new-lamassu-admin/src/pages/Services/schemas/bitgo.js b/new-lamassu-admin/src/pages/Services/schemas/bitgo.js new file mode 100644 index 00000000..66e4b9e6 --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/bitgo.js @@ -0,0 +1,120 @@ +import * as Yup from 'yup' + +import SecretInputFormik from 'src/components/inputs/formik/SecretInput' +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +const isDefined = it => it && it.length + +export default { + code: 'bitgo', + name: 'BitGo', + title: 'BitGo (Wallet)', + elements: [ + { + code: 'token', + display: 'API Token', + component: TextInputFormik, + face: true, + long: true + }, + { + code: 'environment', + display: 'Environment', + component: TextInputFormik, + face: true + }, + { + code: 'btcWalletId', + display: 'BTC Wallet ID', + component: TextInputFormik + }, + { + code: 'btcWalletPassphrase', + display: 'BTC Wallet Passphrase', + component: SecretInputFormik + }, + { + code: 'ltcWalletId', + display: 'LTC Wallet ID', + component: TextInputFormik + }, + { + code: 'ltcWalletPassphrase', + display: 'LTC Wallet Passphrase', + component: SecretInputFormik + }, + { + code: 'zecWalletId', + display: 'ZEC Wallet ID', + component: TextInputFormik + }, + { + code: 'zecWalletPassphrase', + display: 'ZEC Wallet Passphrase', + component: SecretInputFormik + }, + { + code: 'bchWalletId', + display: 'BCH Wallet ID', + component: TextInputFormik + }, + { + code: 'bchWalletPassphrase', + display: 'BCH Wallet Passphrase', + component: SecretInputFormik + }, + { + code: 'dashWalletId', + display: 'DASH Wallet ID', + component: TextInputFormik + }, + { + code: 'dashWalletPassphrase', + display: 'DASH Wallet Passphrase', + component: SecretInputFormik + } + ], + validationSchema: Yup.object().shape({ + token: Yup.string() + .max(100, 'Too long') + .required('Required'), + btcWalletId: Yup.string().max(100, 'Too long'), + btcWalletPassphrase: Yup.string() + .max(100, 'Too long') + .when('btcWalletId', { + is: isDefined, + then: Yup.string().required() + }), + ltcWalletId: Yup.string().max(100, 'Too long'), + ltcWalletPassphrase: Yup.string() + .max(100, 'Too long') + .when('ltcWalletId', { + is: isDefined, + then: Yup.string().required() + }), + zecWalletId: Yup.string().max(100, 'Too long'), + zecWalletPassphrase: Yup.string() + .max(100, 'Too long') + .when('zecWalletId', { + is: isDefined, + then: Yup.string().required() + }), + bchWalletId: Yup.string().max(100, 'Too long'), + bchWalletPassphrase: Yup.string() + .max(100, 'Too long') + .when('bchWalletId', { + is: isDefined, + then: Yup.string().required() + }), + dashWalletId: Yup.string().max(100, 'Too long'), + dashWalletPassphrase: Yup.string() + .max(100, 'Too long') + .when('dashWalletId', { + is: isDefined, + then: Yup.string().required() + }), + environment: Yup.string() + .matches(/(prod|test)/) + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/bitstamp.js b/new-lamassu-admin/src/pages/Services/schemas/bitstamp.js new file mode 100644 index 00000000..bf91b2f2 --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/bitstamp.js @@ -0,0 +1,43 @@ +import * as Yup from 'yup' + +import SecretInputFormik from 'src/components/inputs/formik/SecretInput' +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +export default { + code: 'bitstamp', + name: 'Bitstamp', + title: 'Bitstamp (Exchange)', + elements: [ + { + code: 'clientId', + display: 'Client ID', + component: TextInputFormik, + face: true, + long: true + }, + { + code: 'key', + display: 'API Key', + component: TextInputFormik, + face: true, + long: true + }, + { + code: 'secret', + display: 'API Secret', + component: SecretInputFormik + } + ], + + validationSchema: Yup.object().shape({ + clientId: Yup.string() + .max(100, 'Too long') + .required('Required'), + key: Yup.string() + .max(100, 'Too long') + .required('Required'), + secret: Yup.string() + .max(100, 'Too long') + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/blockcypher.js b/new-lamassu-admin/src/pages/Services/schemas/blockcypher.js new file mode 100644 index 00000000..dd6107af --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/blockcypher.js @@ -0,0 +1,34 @@ +import * as Yup from 'yup' + +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +export default { + code: 'blockcypher', + name: 'Blockcypher', + title: 'Blockcypher (Payments)', + elements: [ + { + code: 'token', + display: 'API Token', + component: TextInputFormik, + face: true, + long: true + }, + { + code: 'confidenceFactor', + display: 'Confidence Factor', + component: TextInputFormik, + face: true + } + ], + + validationSchema: Yup.object().shape({ + token: Yup.string() + .max(100, 'Too long') + .required('Required'), + confidenceFactor: Yup.number() + .integer('Please input a positive integer') + .positive('Please input a positive integer') + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/index.js b/new-lamassu-admin/src/pages/Services/schemas/index.js new file mode 100644 index 00000000..e3dd4cd4 --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/index.js @@ -0,0 +1,21 @@ +import bitgo from './bitgo' +import bitstamp from './bitstamp' +import blockcypher from './blockcypher' +import infura from './infura' +import itbit from './itbit' +import kraken from './kraken' +import mailgun from './mailgun' +import strike from './strike' +import twilio from './twilio' + +export default { + [bitgo.code]: bitgo, + [bitstamp.code]: bitstamp, + [blockcypher.code]: blockcypher, + [infura.code]: infura, + [itbit.code]: itbit, + [kraken.code]: kraken, + [mailgun.code]: mailgun, + [strike.code]: strike, + [twilio.code]: twilio +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/infura.js b/new-lamassu-admin/src/pages/Services/schemas/infura.js new file mode 100644 index 00000000..63e453a8 --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/infura.js @@ -0,0 +1,41 @@ +import * as Yup from 'yup' + +import SecretInputFormik from 'src/components/inputs/formik/SecretInput' +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +export default { + code: 'infura', + name: 'Infura', + title: 'Infura (Wallet)', + elements: [ + { + code: 'apiKey', + display: 'API Key', + component: TextInputFormik, + face: true, + long: true + }, + { + code: 'apiSecret', + display: 'API Secret', + component: SecretInputFormik + }, + { + code: 'endpoint', + display: 'Endpoint', + component: TextInputFormik, + face: true + } + ], + validationSchema: Yup.object().shape({ + apiKey: Yup.string() + .max(100, 'Too long') + .required('Required'), + apiSecret: Yup.string() + .max(100, 'Too long') + .required('Required'), + endpoint: Yup.string() + .max(100, 'Too long') + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/itbit.js b/new-lamassu-admin/src/pages/Services/schemas/itbit.js new file mode 100644 index 00000000..283d4374 --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/itbit.js @@ -0,0 +1,50 @@ +import * as Yup from 'yup' + +import SecretInputFormik from 'src/components/inputs/formik/SecretInput' +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +export default { + code: 'itbit', + name: 'Itbit', + title: 'Itbit (Exchange)', + elements: [ + { + code: 'userId', + display: 'User ID', + component: TextInputFormik, + face: true, + long: true + }, + { + code: 'walletId', + display: 'Wallet ID', + component: TextInputFormik, + face: true, + long: true + }, + { + code: 'clientKey', + display: 'Client Key', + component: TextInputFormik + }, + { + code: 'clientSecret', + display: 'Client Secret', + component: SecretInputFormik + } + ], + validationSchema: Yup.object().shape({ + userId: Yup.string() + .max(100, 'Too long') + .required('Required'), + walletId: Yup.string() + .max(100, 'Too long') + .required('Required'), + clientKey: Yup.string() + .max(100, 'Too long') + .required('Required'), + clientSecret: Yup.string() + .max(100, 'Too long') + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/kraken.js b/new-lamassu-admin/src/pages/Services/schemas/kraken.js new file mode 100644 index 00000000..7a00bd8f --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/kraken.js @@ -0,0 +1,32 @@ +import * as Yup from 'yup' + +import SecretInputFormik from 'src/components/inputs/formik/SecretInput' +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +export default { + code: 'kraken', + name: 'Kraken', + title: 'Kraken (Exchange)', + elements: [ + { + code: 'apiKey', + display: 'API Key', + component: TextInputFormik, + face: true, + long: true + }, + { + code: 'privateKey', + display: 'Private Key', + component: SecretInputFormik + } + ], + validationSchema: Yup.object().shape({ + apiKey: Yup.string() + .max(100, 'Too long') + .required('Required'), + privateKey: Yup.string() + .max(100, 'Too long') + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/mailgun.js b/new-lamassu-admin/src/pages/Services/schemas/mailgun.js new file mode 100644 index 00000000..b9f43f4b --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/mailgun.js @@ -0,0 +1,49 @@ +import * as Yup from 'yup' + +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +export default { + code: 'mailgun', + name: 'Mailgun', + title: 'Mailgun (Email)', + elements: [ + { + code: 'apiKey', + display: 'API Key', + component: TextInputFormik + }, + { + code: 'domain', + display: 'Domain', + component: TextInputFormik + }, + { + code: 'fromEmail', + display: 'From Email', + component: TextInputFormik, + face: true + }, + { + code: 'toEmail', + display: 'To Email', + component: TextInputFormik, + face: true + } + ], + validationSchema: Yup.object().shape({ + apiKey: Yup.string() + .max(100, 'Too long') + .required('Required'), + domain: Yup.string() + .max(100, 'Too long') + .required('Required'), + fromEmail: Yup.string() + .max(100, 'Too long') + .email('Please input a valid email address') + .required('Required'), + toEmail: Yup.string() + .max(100, 'Too long') + .email('Please input a valid email address') + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/strike.js b/new-lamassu-admin/src/pages/Services/schemas/strike.js new file mode 100644 index 00000000..44cb94be --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/strike.js @@ -0,0 +1,23 @@ +import * as Yup from 'yup' + +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +export default { + code: 'strike', + name: 'Strike', + title: 'Strike (Lightning Payments)', + elements: [ + { + code: 'token', + display: 'API Token', + component: TextInputFormik, + face: true, + long: true + } + ], + validationSchema: Yup.object().shape({ + token: Yup.string() + .max(100, 'Too long') + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Services/schemas/twilio.js b/new-lamassu-admin/src/pages/Services/schemas/twilio.js new file mode 100644 index 00000000..c0bc69e3 --- /dev/null +++ b/new-lamassu-admin/src/pages/Services/schemas/twilio.js @@ -0,0 +1,48 @@ +import * as Yup from 'yup' + +import SecretInputFormik from 'src/components/inputs/formik/SecretInput' +import TextInputFormik from 'src/components/inputs/formik/TextInput' + +export default { + code: 'twilio', + name: 'Twilio', + title: 'Twilio (SMS)', + elements: [ + { + code: 'accountSid', + display: 'Account SID', + component: TextInputFormik + }, + { + code: 'authToken', + display: 'Auth Token', + component: SecretInputFormik + }, + { + code: 'fromNumber', + display: 'From Number', + component: TextInputFormik, + face: true + }, + { + code: 'toNumber', + display: 'To Number', + component: TextInputFormik, + face: true + } + ], + validationSchema: Yup.object().shape({ + accountSid: Yup.string() + .max(100, 'Too long') + .required('Required'), + authToken: Yup.string() + .max(100, 'Too long') + .required('Required'), + fromNumber: Yup.string() + .max(100, 'Too long') + .required('Required'), + toNumber: Yup.string() + .max(100, 'Too long') + .required('Required') + }) +} diff --git a/new-lamassu-admin/src/pages/Transactions/Transactions.js b/new-lamassu-admin/src/pages/Transactions/Transactions.js index 2b5a3b14..50e84533 100644 --- a/new-lamassu-admin/src/pages/Transactions/Transactions.js +++ b/new-lamassu-admin/src/pages/Transactions/Transactions.js @@ -9,7 +9,7 @@ import React, { useState } from 'react' import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper' import Title from 'src/components/Title' import { FeatureButton } from 'src/components/buttons' -import ExpTable from 'src/components/expandable-table/ExpTable' +import DataTable from 'src/components/tables/DataTable' import { ReactComponent as DownloadInverseIcon } from 'src/styling/icons/button/download/white.svg' import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg' import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' @@ -71,29 +71,34 @@ const Transactions = () => { { header: '', width: 62, + size: 'sm', view: it => (it.txClass === 'cashOut' ? : ) }, { header: 'Machine', name: 'machineName', width: 180, + size: 'sm', view: R.path(['machineName']) }, { header: 'Customer', width: 162, + size: 'sm', view: getCustomerDisplayName }, { header: 'Cash', width: 110, textAlign: 'right', + size: 'sm', view: it => `${Number.parseFloat(it.fiat)} ${it.fiatCode}` }, { header: 'Crypto', width: 141, textAlign: 'right', + size: 'sm', view: it => `${toUnit(new BigNumber(it.cryptoAtoms), it.cryptoCode).toFormat(5)} ${ it.cryptoCode @@ -103,27 +108,22 @@ const Transactions = () => { header: 'Address', view: R.path(['toAddress']), className: classes.overflowTd, + size: 'sm', width: 136 }, { header: 'Date (UTC)', view: it => moment.utc(it.created).format('YYYY-MM-D'), textAlign: 'right', + size: 'sm', width: 124 }, { header: 'Time (UTC)', view: it => moment.utc(it.created).format('HH:mm:ss'), textAlign: 'right', + size: 'sm', width: 124 - }, - { - header: '', // Trade - view: () => {}, - width: 90 - }, - { - width: 71 } ] @@ -176,10 +176,11 @@ const Transactions = () => {
- ) diff --git a/new-lamassu-admin/src/pages/Wallet/Wallet.js b/new-lamassu-admin/src/pages/Wallet/Wallet.js new file mode 100644 index 00000000..9285969c --- /dev/null +++ b/new-lamassu-admin/src/pages/Wallet/Wallet.js @@ -0,0 +1,99 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { gql } from 'apollo-boost' +import * as R from 'ramda' +import React, { useState } from 'react' + +import { NamespacedTable as EditableTable } from 'src/components/editableTable' +import TitleSection from 'src/components/layout/TitleSection' +import { fromNamespace, toNamespace } from 'src/utils/config' + +import Wizard from './Wizard' +import { WalletSchema, getElements } from './helper' + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject, $accounts: [JSONObject]) { + saveConfig(config: $config) + saveAccounts(accounts: $accounts) + } +` + +const GET_INFO = gql` + query getData { + config + accounts + accountsConfig { + code + display + class + cryptos + } + cryptoCurrencies { + code + display + } + } +` + +const Wallet = ({ name: SCREEN_KEY }) => { + const [wizard, setWizard] = useState(false) + const [error, setError] = useState(false) + const { data } = useQuery(GET_INFO) + + const [saveConfig] = useMutation(SAVE_CONFIG, { + onCompleted: () => setWizard(false), + onError: () => setError(true), + refetchQueries: () => ['getData'] + }) + + const save = (rawConfig, accounts) => { + const config = toNamespace(SCREEN_KEY)(rawConfig) + setError(false) + return saveConfig({ variables: { config, accounts } }) + } + + const config = data?.config && fromNamespace(SCREEN_KEY)(data.config) + const accountsConfig = data?.accountsConfig + const cryptoCurrencies = data?.cryptoCurrencies ?? [] + const accounts = data?.accounts ?? [] + + const onToggle = id => { + const namespaced = fromNamespace(id)(config) + if (!WalletSchema.isValidSync(namespaced)) return setWizard(id) + save(toNamespace(id, { active: !namespaced?.active })) + } + + return ( + <> + + !WalletSchema.isValidSync(it)} + enableEdit + editWidth={134} + enableToggle + toggleWidth={109} + onToggle={onToggle} + save={save} + validationSchema={WalletSchema} + disableRowEdit={R.compose(R.not, R.path(['active']))} + elements={getElements(cryptoCurrencies, accountsConfig)} + /> + {wizard && ( + setWizard(false)} + save={save} + error={error} + cryptoCurrencies={cryptoCurrencies} + userAccounts={data?.config?.accounts} + accounts={accounts} + accountsConfig={accountsConfig} + /> + )} + + ) +} + +export default Wallet diff --git a/new-lamassu-admin/src/pages/Wallet/WalletSettings.js b/new-lamassu-admin/src/pages/Wallet/WalletSettings.js deleted file mode 100644 index 4baed6e4..00000000 --- a/new-lamassu-admin/src/pages/Wallet/WalletSettings.js +++ /dev/null @@ -1,451 +0,0 @@ -import { useQuery, useMutation } from '@apollo/react-hooks' -import { makeStyles } from '@material-ui/core' -import { gql } from 'apollo-boost' -import * as R from 'ramda' -import React, { useState } from 'react' - -import ErrorMessage from 'src/components/ErrorMessage' -import Modal from 'src/components/Modal' -import Title from 'src/components/Title' -import { - Table, - THead, - Th, - TBody, - Tr, - Td -} from 'src/components/fake-table/Table' -import { Switch } from 'src/components/inputs' -import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg' -import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' -import { zircon } from 'src/styling/variables' - -import Wizard from './Wizard' -import WizardSplash from './WizardSplash' -import { - CRYPTOCURRENCY_KEY, - TICKER_KEY, - WALLET_KEY, - EXCHANGE_KEY, - ZERO_CONF_KEY, - EDIT_KEY, - ENABLE_KEY, - SIZE_KEY, - TEXT_ALIGN_KEY -} from './aux.js' - -const styles = { - disabledDrawing: { - position: 'relative', - display: 'flex', - alignItems: 'center', - '& > div': { - position: 'absolute', - backgroundColor: zircon, - height: 36, - width: 678 - } - }, - modal: { - width: 544 - }, - switchErrorMessage: { - margin: [['auto', 0, 'auto', 20]] - } -} - -const useStyles = makeStyles(styles) - -const columns = { - [CRYPTOCURRENCY_KEY]: { - [SIZE_KEY]: 182, - [TEXT_ALIGN_KEY]: 'left' - }, - [TICKER_KEY]: { - [SIZE_KEY]: 182, - [TEXT_ALIGN_KEY]: 'left' - }, - [WALLET_KEY]: { - [SIZE_KEY]: 182, - [TEXT_ALIGN_KEY]: 'left' - }, - [EXCHANGE_KEY]: { - [SIZE_KEY]: 182, - [TEXT_ALIGN_KEY]: 'left' - }, - [ZERO_CONF_KEY]: { - [SIZE_KEY]: 229, - [TEXT_ALIGN_KEY]: 'left' - }, - [EDIT_KEY]: { - [SIZE_KEY]: 134, - [TEXT_ALIGN_KEY]: 'center' - }, - [ENABLE_KEY]: { - [SIZE_KEY]: 109, - [TEXT_ALIGN_KEY]: 'center' - } -} - -const GET_INFO = gql` - { - config - accounts { - code - display - class - cryptos - } - cryptoCurrencies { - code - display - } - } -` - -const SAVE_CONFIG = gql` - mutation Save($config: JSONObject) { - saveConfig(config: $config) - } -` - -const schema = { - [TICKER_KEY]: '', - [WALLET_KEY]: '', - [EXCHANGE_KEY]: '', - [ZERO_CONF_KEY]: '', - [ENABLE_KEY]: false -} - -const WalletSettings = () => { - const [cryptoCurrencies, setCryptoCurrencies] = useState(null) - const [accounts, setAccounts] = useState(null) - const [services, setServices] = useState(null) - const [state, setState] = useState(null) - const [modalContent, setModalContent] = useState(null) - const [modalOpen, setModalOpen] = useState(false) - const [error, setError] = useState(null) - const [saveConfig] = useMutation(SAVE_CONFIG, { - onCompleted: data => { - setServices(data.saveConfig.accounts) - setError(null) - } - }) - - useQuery(GET_INFO, { - onCompleted: data => { - const { cryptoCurrencies, config, accounts } = data - - const wallet = config?.wallet ?? [] - const services = config?.accounts ?? {} - - const newState = R.map(crypto => { - const el = R.find(R.propEq(CRYPTOCURRENCY_KEY, crypto.code))(wallet) - if (!el) return R.assoc(CRYPTOCURRENCY_KEY, crypto.code)(schema) - return el - })(cryptoCurrencies) - - setState(newState) - setCryptoCurrencies(cryptoCurrencies) - setAccounts(accounts) - setServices(services) - }, - onError: error => console.error(error) - }) - - const classes = useStyles() - - const getSize = key => columns[key][SIZE_KEY] - const getTextAlign = key => columns[key][TEXT_ALIGN_KEY] - - const getDisplayName = list => code => - R.path(['display'], R.find(R.propEq('code', code), list)) - - const getCryptoDisplayName = row => - getDisplayName(cryptoCurrencies)(row[CRYPTOCURRENCY_KEY]) - - const getNoSetUpNeeded = accounts => { - const needs = [ - 'bitgo', - 'bitstamp', - 'blockcypher', - 'infura', - 'kraken', - 'strike' - ] - return R.filter(account => !R.includes(account.code, needs), accounts) - } - const getAlreadySetUp = serviceClass => cryptocode => { - const possible = R.filter( - service => R.includes(cryptocode, service.cryptos), - R.filter(R.propEq('class', serviceClass), accounts) - ) - const isSetUp = service => R.includes(service.code, R.keys(services)) - const alreadySetUp = R.filter(isSetUp, possible) - const join = [...alreadySetUp, ...getNoSetUpNeeded(possible)] - return R.isEmpty(join) ? null : join - } - const getNotSetUp = serviceClass => cryptocode => { - const possible = R.filter( - service => R.includes(cryptocode, service.cryptos), - R.filter(R.propEq('class', serviceClass), accounts) - ) - const without = R.without( - getAlreadySetUp(serviceClass)(cryptocode) ?? [], - possible - ) - return R.isEmpty(without) ? null : without - } - - const saveNewService = (code, it) => { - const newAccounts = R.clone(services) - newAccounts[code] = it - return saveConfig({ variables: { config: { accounts: newAccounts } } }) - } - const save = it => { - const idx = R.findIndex( - R.propEq(CRYPTOCURRENCY_KEY, it[CRYPTOCURRENCY_KEY]), - state - ) - const merged = R.mergeDeepRight(state[idx], it) - const updated = R.update(idx, merged, state) - return saveConfig({ - variables: { config: { wallet: updated } } - }) - } - - const isSet = crypto => - crypto[TICKER_KEY] && - crypto[WALLET_KEY] && - crypto[EXCHANGE_KEY] && - crypto[ZERO_CONF_KEY] - - const handleEnable = row => event => { - if (!isSet(row)) { - setModalContent( - - ) - setModalOpen(true) - setError(null) - return - } - - save(R.assoc(ENABLE_KEY, event.target.checked, row)).catch(error => - setError(error) - ) - } - - const handleEditClick = row => { - setModalOpen(true) - handleModalNavigation(row)(1) - } - - const handleModalClose = () => { - setModalOpen(false) - setModalContent(null) - } - const handleModalNavigation = row => currentPage => { - const cryptocode = row[CRYPTOCURRENCY_KEY] - - switch (currentPage) { - case 1: - setModalContent( - R.includes(cryptocode, ticker.cryptos), - R.filter(R.propEq('class', 'ticker'), accounts) - )} - /> - ) - break - case 2: - setModalContent( - - ) - break - case 3: - setModalContent( - - ) - break - case 4: - setModalContent( - - ) - break - case 5: - // Zero Conf - return save(R.assoc(ENABLE_KEY, true, row)).then(m => { - setModalOpen(false) - setModalContent(null) - }) - default: - break - } - - return new Promise(() => {}) - } - - if (!state) return null - - return ( - <> -
-
- Wallet Settings - {error && !modalOpen && ( - - Failed to save - - )} -
-
-
- - - - - - - - - - - - {state.map((row, idx) => ( - - - {!isSet(row) && ( - - )} - {isSet(row) && ( - <> - - - - - - )} - - - - ))} - -
- Cryptocurrency - - Ticker - - Wallet - - Exchange - - Zero Conf - - Edit - - Enable -
- {getCryptoDisplayName(row)} - -
-
- {getDisplayName(accounts)(row[TICKER_KEY])} - - {getDisplayName(accounts)(row[WALLET_KEY])} - - {getDisplayName(accounts)(row[EXCHANGE_KEY])} - - {getDisplayName(accounts)(row[ZERO_CONF_KEY])} - - {!isSet(row) && } - {isSet(row) && ( - - )} - - -
-
- - {modalContent} - - - ) -} - -export default WalletSettings diff --git a/new-lamassu-admin/src/pages/Wallet/Wizard.js b/new-lamassu-admin/src/pages/Wallet/Wizard.js index 109d72bb..71fddde4 100644 --- a/new-lamassu-admin/src/pages/Wallet/Wizard.js +++ b/new-lamassu-admin/src/pages/Wallet/Wizard.js @@ -1,306 +1,107 @@ -import { makeStyles } from '@material-ui/core' -import classnames from 'classnames' -import { Formik, Field as FormikField } from 'formik' import * as R from 'ramda' -import React, { useState, useEffect } from 'react' +import React, { useState } from 'react' -import ErrorMessage from 'src/components/ErrorMessage' -import Stage from 'src/components/Stage' -import { Button } from 'src/components/buttons' -import { RadioGroup, AutocompleteSelect } from 'src/components/inputs' -import { H1, Info2, H4 } from 'src/components/typography' -import { startCase } from 'src/utils/string' +import Modal from 'src/components/Modal' +import schema from 'src/pages/Services/schemas' +import { toNamespace } from 'src/utils/config' -import { getBitgoFields, getBitgoFormik } from '../Services/Bitgo' -import { getBitstampFields, getBitstampFormik } from '../Services/Bitstamp' -import { - getBlockcypherFields, - getBlockcypherFormik -} from '../Services/Blockcypher' -import { getInfuraFields, getInfuraFormik } from '../Services/Infura' -import { getKrakenFields, getKrakenFormik } from '../Services/Kraken' -import { getStrikeFields, getStrikeFormik } from '../Services/Strike' +import WizardSplash from './WizardSplash' +import WizardStep from './WizardStep' -const styles = { - modalContent: { - display: 'flex', - flexDirection: 'column', - padding: [[24, 32, 0]], - '& > h1': { - margin: [[0, 0, 10]] - }, - '& > h4': { - margin: [[32, 0, 32 - 9, 0]] - }, - '& > p': { - margin: 0 - } - }, - submitButtonWrapper: { - display: 'flex', - alignSelf: 'flex-end', - margin: [['auto', 0, 0]] - }, - submitButton: { - width: 67, - padding: [[0, 0]], - margin: [['auto', 0, 24, 20]], - '&:active': { - margin: [['auto', 0, 24, 20]] - } - }, - stages: { - marginTop: 10 - }, - radios: { - display: 'flex' - }, - radiosAsColumn: { - flexDirection: 'column' - }, - radiosAsRow: { - flexDirection: 'row' - }, - alreadySetupRadioButtons: { - display: 'flex', - flexDirection: 'row' - }, - selectNewWrapper: { - display: 'flex', - alignItems: 'center' - }, - selectNew: { - width: 204, - flexGrow: 0, - bottom: 7 - }, - newServiceForm: { - display: 'flex', - flexDirection: 'column' - }, - newServiceFormFields: { - marginTop: 20, - marginBottom: 48 - }, - field: { - '&:not(:last-child)': { - marginBottom: 20 - } - }, - formInput: { - '& .MuiInputBase-input': { - width: 426 - } - } +const LAST_STEP = 4 +const MODAL_WIDTH = 554 + +const contains = crypto => R.compose(R.contains(crypto), R.prop('cryptos')) +const sameClass = type => R.propEq('class', type) +const filterConfig = (crypto, type) => + R.filter(it => sameClass(type)(it) && contains(crypto)(it)) + +const getItems = (accountsConfig, accounts, type, crypto) => { + const fConfig = filterConfig(crypto, type)(accountsConfig) + const find = code => R.find(R.propEq('code', code))(accounts) + + const [filled, unfilled] = R.partition(({ code }) => { + const account = find(code) + if (!schema[code]) return true + + const { validationSchema } = schema[code] + return validationSchema.isValidSync(account) + })(fConfig) + + return { filled, unfilled } } -const getNewServiceForm = serviceName => { - switch (serviceName) { - case 'bitgo': - return { fields: getBitgoFields(), formik: getBitgoFormik() } - case 'bitstamp': - return { fields: getBitstampFields(), formik: getBitstampFormik() } - case 'blockcypher': - return { fields: getBlockcypherFields(), formik: getBlockcypherFormik() } - case 'infura': - return { fields: getInfuraFields(), formik: getInfuraFormik() } - case 'kraken': - return { fields: getKrakenFields(), formik: getKrakenFormik() } - case 'strike': - return { fields: getStrikeFields(), formik: getStrikeFormik() } - default: +const Wizard = ({ coin, onClose, accountsConfig, accounts, save, error }) => { + const [{ step, config, accountsToSave }, setState] = useState({ + step: 0, + config: { active: true }, + accountsToSave: [] + }) + + const title = `Enable ${coin.display}` + const isLastStep = step === LAST_STEP + + const tickers = { filled: filterConfig(coin.code, 'ticker')(accountsConfig) } + const wallets = getItems(accountsConfig, accounts, 'wallet', coin.code) + const exchanges = getItems(accountsConfig, accounts, 'exchange', coin.code) + const zeroConfs = getItems(accountsConfig, accounts, 'zeroConf', coin.code) + + const getValue = code => R.find(R.propEq('code', code))(accounts) + + const onContinue = async (it, it2) => { + const newConfig = R.merge(config, it) + const newAccounts = it2 ? R.concat(accountsToSave, [it2]) : accountsToSave + + if (isLastStep) { + return save(toNamespace(coin.code, newConfig), newAccounts) + } + + setState({ + step: step + 1, + config: newConfig, + accountsToSave: newAccounts + }) } -} -const useStyles = makeStyles(styles) - -const SubmitButton = ({ error, ...props }) => { - const classes = useStyles() + const getStepData = () => { + switch (step) { + case 1: + return { type: 'ticker', ...tickers } + case 2: + return { type: 'wallet', ...wallets } + case 3: + return { type: 'exchange', ...exchanges } + case 4: + return { type: 'zeroConf', name: 'zero conf', ...zeroConfs } + default: + return null + } + } return ( -
- {error && Failed to save} - -
- ) -} - -const Wizard = ({ - crypto, - coinName, - pageName, - currentStage, - alreadySetUp, - notSetUp, - handleModalNavigation, - saveNewService -}) => { - const [selectedRadio, setSelectedRadio] = useState( - crypto[pageName] !== '' ? crypto[pageName] : null - ) - useEffect(() => { - setFormContent(null) - setSelectedFromDropdown(null) - setSetUpNew('') - setSelectedRadio(crypto[pageName] !== '' ? crypto[pageName] : null) - }, [crypto, pageName]) - const [setUpNew, setSetUpNew] = useState(null) - const [selectedFromDropdown, setSelectedFromDropdown] = useState(null) - const [formContent, setFormContent] = useState(null) - const [error, setError] = useState(null) - - const classes = useStyles() - - const radiosClassNames = { - [classes.radios]: true, - [classes.radiosAsColumn]: !selectedFromDropdown, - [classes.radiosAsRow]: selectedFromDropdown - } - - const radioButtonOptions = - alreadySetUp && - R.map(el => { - return { label: el.display, value: el.code } - })(alreadySetUp) - - const handleRadioButtons = event => { - R.o(setSelectedRadio, R.path(['target', 'value']))(event) - setSetUpNew('') - setFormContent(null) - setSelectedFromDropdown(null) - setError(null) - } - - const handleSetUpNew = event => { - R.o(setSetUpNew, R.path(['target', 'value']))(event) - setSelectedRadio('') - setFormContent(null) - setSelectedFromDropdown(null) - setError(null) - } - - const handleNext = value => event => { - const nav = handleModalNavigation( - R.mergeDeepRight(crypto, { [pageName]: value }) - )(currentStage + 1) - - nav.catch(error => setError(error)) - } - - const handleSelectFromDropdown = it => { - setSelectedFromDropdown(it) - setFormContent(getNewServiceForm(it?.code)) - setError(null) - } - - const isSubmittable = () => { - if (selectedRadio) return true - if (!selectedRadio && selectedFromDropdown && !formContent) return true - return false - } - - console.log(formContent) - - return ( -
-

Enable {coinName}

- {startCase(pageName)} - -

{`Select a ${pageName} or set up a new one`}

-
- {alreadySetUp && ( - - )} - {notSetUp && ( -
- - {setUpNew && ( - - )} -
- )} -
- {formContent && ( - - saveNewService(selectedFromDropdown.code, values) - .then(m => { - handleNext(selectedFromDropdown.code)() - }) - .catch(error => setError(error)) - }> - {props => ( -
-
- {formContent.fields.map((field, idx) => ( -
- { - setError(null) - }} - /> -
- ))} -
- - - )} -
- )} - {!formContent && ( - + {step === 0 && ( + onContinue()} /> )} -
+ {step !== 0 && ( + + )} + ) } diff --git a/new-lamassu-admin/src/pages/Wallet/WizardSplash.js b/new-lamassu-admin/src/pages/Wallet/WizardSplash.js index 40bfa2e8..5e4614de 100644 --- a/new-lamassu-admin/src/pages/Wallet/WizardSplash.js +++ b/new-lamassu-admin/src/pages/Wallet/WizardSplash.js @@ -11,71 +11,64 @@ import { ReactComponent as LitecoinLogo } from 'src/styling/logos/icon-litecoin- import { ReactComponent as ZCashLogo } from 'src/styling/logos/icon-zcash-colour.svg' const styles = { - logoWrapper: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: 80, - margin: [[40, 0, 24]], - '& > svg': { - maxHeight: '100%', - width: '100%' - } + logo: { + maxHeight: 80, + maxWidth: 200 + }, + title: { + margin: [[24, 0, 32, 0]] + }, + text: { + margin: 0 + }, + button: { + marginTop: 'auto', + marginBottom: 58 }, modalContent: { display: 'flex', flexDirection: 'column', alignItems: 'center', - padding: [[0, 66]], - '& > h1': { - margin: [[0, 0, 32]] - }, - '& > p': { - margin: 0 - }, - '& > button': { - margin: [['auto', 0, 56]], - '&:active': { - margin: [['auto', 0, 56]] - } - } + padding: [[0, 42]], + flex: 1 } } const useStyles = makeStyles(styles) -const renderLogo = code => { +const getLogo = code => { switch (code) { case 'BTC': - return + return BitcoinLogo case 'BCH': - return + return BitcoinCashLogo case 'DASH': - return + return DashLogo case 'ETH': - return + return EthereumLogo case 'LTC': - return + return LitecoinLogo case 'ZEC': - return + return ZCashLogo default: return null } } -const WizardSplash = ({ code, coinName, handleModalNavigation }) => { +const WizardSplash = ({ code, name, onContinue }) => { const classes = useStyles() + const Logo = getLogo(code) return (
-
{renderLogo(code)}
-

Enable {coinName}

-

- You are about to enable {coinName} on your system. This will allow you - to use this cryptocurrency on your machines. To able to do that, you’ll + +

Enable {name}

+

+ You are about to enable {name} on your system. This will allow you to + use this cryptocurrency on your machines. To be able to do that, you’ll have to setup all the necessary 3rd party services.

-
diff --git a/new-lamassu-admin/src/pages/Wallet/WizardStep.js b/new-lamassu-admin/src/pages/Wallet/WizardStep.js new file mode 100644 index 00000000..4482764f --- /dev/null +++ b/new-lamassu-admin/src/pages/Wallet/WizardStep.js @@ -0,0 +1,155 @@ +import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' +import * as R from 'ramda' +import React, { useReducer, useEffect } from 'react' + +import ErrorMessage from 'src/components/ErrorMessage' +import Stepper from 'src/components/Stepper' +import { Button } from 'src/components/buttons' +import { RadioGroup, Autocomplete } from 'src/components/inputs' +import { Info2, H4 } from 'src/components/typography' +import FormRenderer from 'src/pages/Services/FormRenderer' +import schema from 'src/pages/Services/schemas' +import { startCase } from 'src/utils/string' + +import styles from './WizardStep.styles' +const useStyles = makeStyles(styles) + +const initialState = { + form: null, + selected: null, + isNew: false, + iError: false +} + +const reducer = (state, action) => { + switch (action.type) { + case 'select': + return { + form: null, + selected: action.selected, + isNew: null, + iError: false + } + case 'new': + return { form: state.form, selected: null, isNew: true, iError: false } + case 'form': + return { + form: action.form, + selected: action.form.code, + isNew: true, + iError: false + } + case 'error': + return R.merge(state, { iError: true }) + case 'reset': + return initialState + default: + throw new Error() + } +} + +const WizardStep = ({ + type, + name, + step, + error, + lastStep, + onContinue, + filled, + unfilled, + getValue +}) => { + const classes = useStyles() + const [{ iError, selected, form, isNew }, dispatch] = useReducer( + reducer, + initialState + ) + + useEffect(() => { + dispatch({ type: 'reset' }) + }, [step]) + + const iContinue = (config, account) => { + if (!config || !config[type]) { + return dispatch({ type: 'error' }) + } + onContinue(config, account) + } + + const label = lastStep ? 'Finish' : 'Next' + const displayName = name ?? type + const subtitleClass = { + [classes.subtitle]: true, + [classes.error]: iError + } + + return ( + <> + {startCase(type)} + +

+ Select a {displayName} or set up a new one +

+ { + dispatch({ type: 'select', selected: it }) + }} + labelClassName={classes.radioLabel} + radioClassName={classes.radio} + /> +
+ {!R.isEmpty(unfilled) && !R.isNil(unfilled) && ( + { + dispatch({ type: 'new' }) + }} + labelClassName={classes.radioLabel} + radioClassName={classes.radio} + options={[{ display: 'Set up new', code: true }]} + /> + )} + {isNew && ( + { + dispatch({ type: 'form', form: it }) + }} + /> + )} +
+ {form && ( + + iContinue({ [type]: form.code }, R.merge(it, { code: form.code })) + } + elements={schema[form.code].elements} + validationSchema={schema[form.code].validationSchema} + value={getValue(form.code)} + buttonLabel={label} + /> + )} + {!form && ( +
+ {error && Failed to save} + +
+ )} + + ) +} + +export default WizardStep diff --git a/new-lamassu-admin/src/pages/Wallet/WizardStep.styles.js b/new-lamassu-admin/src/pages/Wallet/WizardStep.styles.js new file mode 100644 index 00000000..d17205b1 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wallet/WizardStep.styles.js @@ -0,0 +1,42 @@ +import { errorColor } from 'src/styling/variables' + +const LABEL_WIDTH = 150 + +export default { + title: { + margin: [[0, 0, 12, 0]] + }, + subtitle: { + margin: [[32, 0, 21, 0]] + }, + error: { + color: errorColor + }, + button: { + marginLeft: 'auto' + }, + submit: { + display: 'flex', + flexDirection: 'row', + margin: [['auto', 0, 24]] + }, + radioGroup: { + flexDirection: 'row' + }, + radioLabel: { + width: LABEL_WIDTH, + height: 48 + }, + radio: { + padding: 4, + margin: 4 + }, + setupNew: { + display: 'flex', + alignItems: 'center', + height: 48 + }, + picker: { + width: LABEL_WIDTH + } +} diff --git a/new-lamassu-admin/src/pages/Wallet/aux.js b/new-lamassu-admin/src/pages/Wallet/aux.js deleted file mode 100644 index d8706744..00000000 --- a/new-lamassu-admin/src/pages/Wallet/aux.js +++ /dev/null @@ -1,21 +0,0 @@ -const CRYPTOCURRENCY_KEY = 'cryptocurrency' -const TICKER_KEY = 'ticker' -const WALLET_KEY = 'wallet' -const EXCHANGE_KEY = 'exchange' -const ZERO_CONF_KEY = 'zeroConf' -const EDIT_KEY = 'edit' -const ENABLE_KEY = 'enabled' -const SIZE_KEY = 'size' -const TEXT_ALIGN_KEY = 'textAlign' - -export { - CRYPTOCURRENCY_KEY, - TICKER_KEY, - WALLET_KEY, - EXCHANGE_KEY, - ZERO_CONF_KEY, - EDIT_KEY, - ENABLE_KEY, - SIZE_KEY, - TEXT_ALIGN_KEY -} diff --git a/new-lamassu-admin/src/pages/Wallet/helper.js b/new-lamassu-admin/src/pages/Wallet/helper.js new file mode 100644 index 00000000..8efd0e9f --- /dev/null +++ b/new-lamassu-admin/src/pages/Wallet/helper.js @@ -0,0 +1,103 @@ +import * as R from 'ramda' +import * as Yup from 'yup' + +import Autocomplete from 'src/components/inputs/formik/Autocomplete.js' + +const filterClass = type => R.filter(it => it.class === type) +const filterCoins = ({ id }) => R.filter(it => R.contains(id)(it.cryptos)) + +const WalletSchema = Yup.object().shape({ + ticker: Yup.string().required('Required'), + wallet: Yup.string().required('Required'), + exchange: Yup.string().required('Required'), + zeroConf: Yup.string().required('Required') +}) + +const getElements = (cryptoCurrencies, accounts) => { + const viewCryptoCurrency = it => + R.compose( + R.prop(['display']), + R.find(R.propEq('code', it)) + )(cryptoCurrencies) + + const filterOptions = type => filterClass(type)(accounts || []) + + const getDisplayName = type => it => + R.compose( + R.prop('display'), + R.find(R.propEq('code', it)) + )(filterOptions(type)) + + const getOptions = R.curry((option, it) => + filterCoins(it)(filterOptions(option)) + ) + + return [ + { + name: 'id', + header: 'Cryptocurrency', + width: 180, + view: viewCryptoCurrency, + size: 'sm', + editable: false + }, + { + name: 'ticker', + size: 'sm', + stripe: true, + view: getDisplayName('ticker'), + width: 190, + input: Autocomplete, + inputProps: { + options: getOptions('ticker'), + valueProp: 'code', + getLabel: R.path(['display']), + limit: null + } + }, + { + name: 'wallet', + size: 'sm', + stripe: true, + view: getDisplayName('wallet'), + width: 190, + input: Autocomplete, + inputProps: { + options: getOptions('wallet'), + valueProp: 'code', + getLabel: R.path(['display']), + limit: null + } + }, + { + name: 'exchange', + size: 'sm', + stripe: true, + view: getDisplayName('exchange'), + width: 190, + input: Autocomplete, + inputProps: { + options: getOptions('exchange'), + valueProp: 'code', + getLabel: R.path(['display']), + limit: null + } + }, + { + name: 'zeroConf', + size: 'sm', + stripe: true, + view: getDisplayName('zeroConf'), + input: Autocomplete, + width: 190, + inputProps: { + options: getOptions('zeroConf'), + valueProp: 'code', + getLabel: R.path(['display']), + limit: null + } + } + ] +} + +export { WalletSchema, getElements, filterClass } diff --git a/new-lamassu-admin/src/pages/maintenance/MachineStatus.js b/new-lamassu-admin/src/pages/maintenance/MachineStatus.js index 224c32f3..78a9d0b1 100644 --- a/new-lamassu-admin/src/pages/maintenance/MachineStatus.js +++ b/new-lamassu-admin/src/pages/maintenance/MachineStatus.js @@ -5,9 +5,10 @@ import moment from 'moment' import * as R from 'ramda' import React from 'react' +import DataTable from 'src/components/tables/DataTable' + import { MainStatus } from '../../components/Status' import Title from '../../components/Title' -import ExpTable from '../../components/expandable-table/ExpTable' import { ReactComponent as WarningIcon } from '../../styling/icons/status/pumpkin.svg' import { ReactComponent as ErrorIcon } from '../../styling/icons/status/tomato.svg' import { mainStyles } from '../Transactions/Transactions.styles' @@ -42,35 +43,37 @@ const MachineStatus = () => { { header: 'Machine Name', width: 232, + size: 'sm', textAlign: 'left', view: m => m.name }, { header: 'Status', width: 349, + size: 'sm', textAlign: 'left', view: m => }, { header: 'Last ping', width: 192, + size: 'sm', textAlign: 'left', view: m => moment(m.lastPing).fromNow() }, { header: 'Ping Time', width: 155, + size: 'sm', textAlign: 'left', view: m => m.pingTime || 'unknown' }, { header: 'Software Version', width: 201, + size: 'sm', textAlign: 'left', view: m => m.softwareVersion || 'unknown' - }, - { - width: 71 } ] @@ -91,10 +94,11 @@ const MachineStatus = () => {
- ) diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js index 3dc6c9e0..73db367c 100644 --- a/new-lamassu-admin/src/routing/routes.js +++ b/new-lamassu-admin/src/routing/routes.js @@ -13,7 +13,7 @@ import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo' import ServerLogs from 'src/pages/ServerLogs' import Services from 'src/pages/Services/Services' import Transactions from 'src/pages/Transactions/Transactions' -import WalletSettings from 'src/pages/Wallet/WalletSettings' +import WalletSettings from 'src/pages/Wallet/Wallet' import MachineStatus from 'src/pages/maintenance/MachineStatus' const tree = [ diff --git a/new-lamassu-admin/src/stories/index.js b/new-lamassu-admin/src/stories/index.js index 8ca172ec..f7aba47c 100644 --- a/new-lamassu-admin/src/stories/index.js +++ b/new-lamassu-admin/src/stories/index.js @@ -13,7 +13,7 @@ import extendJss from 'jss-plugin-extend' import React from 'react' import { ActionButton, Button, Link } from 'src/components/buttons' -import { Radio, TextInput, Switch } from 'src/components/inputs' +import { TextInput, Switch } from 'src/components/inputs' import { ReactComponent as AuthorizeIconReversed } from 'src/styling/icons/button/authorize/white.svg' import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg' @@ -178,8 +178,6 @@ story.add('ConfirmDialog', () => ( )) -story.add('Radio', () => ) - const typographyStory = storiesOf('Typography', module) typographyStory.add('H1', () =>

Hehehe

) diff --git a/new-lamassu-admin/src/styling/global/index.js b/new-lamassu-admin/src/styling/global/index.js index 3e16f474..2113bb75 100644 --- a/new-lamassu-admin/src/styling/global/index.js +++ b/new-lamassu-admin/src/styling/global/index.js @@ -41,6 +41,10 @@ export default { }, 'button::-moz-focus-inner': { border: 0 + }, + // forcing styling onto inner container + '.ReactVirtualized__Grid__innerScrollContainer': { + overflow: 'inherit !important' } } } diff --git a/new-lamassu-admin/src/styling/icons/stripes.svg b/new-lamassu-admin/src/styling/icons/stripes.svg new file mode 100644 index 00000000..f1c87ebd --- /dev/null +++ b/new-lamassu-admin/src/styling/icons/stripes.svg @@ -0,0 +1,28 @@ + + + + + + {' '} + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/logos/icon-bitcoin-colour.svg b/new-lamassu-admin/src/styling/logos/icon-bitcoin-colour.svg index 5b3af8c7..94b562ad 100644 --- a/new-lamassu-admin/src/styling/logos/icon-bitcoin-colour.svg +++ b/new-lamassu-admin/src/styling/logos/icon-bitcoin-colour.svg @@ -1,4 +1,7 @@ - - - - + + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/theme.js b/new-lamassu-admin/src/styling/theme.js index 7a681101..7b2b8dc5 100644 --- a/new-lamassu-admin/src/styling/theme.js +++ b/new-lamassu-admin/src/styling/theme.js @@ -1,5 +1,7 @@ import { createMuiTheme } from '@material-ui/core/styles' +import typographyStyles from 'src/components/typography/styles' + import { backgroundColor, inputFontFamily, @@ -10,9 +12,12 @@ import { fontSize5 } from './variables' +const { p } = typographyStyles + export default createMuiTheme({ typography: { - fontFamily: inputFontFamily + fontFamily: inputFontFamily, + body1: { ...p } }, MuiButtonBase: { disableRipple: true @@ -33,6 +38,11 @@ export default createMuiTheme({ } }, overrides: { + MuiRadio: { + colorSecondary: { + color: secondaryColor + } + }, MuiAutocomplete: { root: { color: fontColor diff --git a/new-lamassu-admin/src/utils/config.js b/new-lamassu-admin/src/utils/config.js index e8c6a22c..3fff3a1d 100644 --- a/new-lamassu-admin/src/utils/config.js +++ b/new-lamassu-admin/src/utils/config.js @@ -15,7 +15,12 @@ const stripl = R.curry((q, str) => const filtered = key => filterByKey(R.startsWith(`${key}_`)) const stripped = key => mapKeys(stripl(`${key}_`)) -const fromServer = key => R.compose(stripped(key), filtered(key)) -const toServer = key => mapKeys(it => `${key}_${it}`) +const fromNamespace = R.curry((key, config) => + R.compose(stripped(key), filtered(key))(config) +) -export { fromServer, toServer } +const toNamespace = R.curry((key, config) => + mapKeys(it => `${key}_${it}`)(config) +) + +export { fromNamespace, toNamespace } diff --git a/new-lamassu-admin/src/utils/string.js b/new-lamassu-admin/src/utils/string.js index 531b7cfd..09dd8360 100644 --- a/new-lamassu-admin/src/utils/string.js +++ b/new-lamassu-admin/src/utils/string.js @@ -2,6 +2,15 @@ import * as R from 'ramda' import S from './sanctuary' +const formatLong = value => { + if (!value || value.length <= 20) return value + + return `${value.slice(0, 8)}(...)${value.slice( + value.length - 8, + value.length + )}` +} + const toFirstLower = S.compose(S.joinWith(''))(R.adjust(0, S.toLower)) const toFirstUpper = S.compose(S.joinWith(''))(R.adjust(0, S.toUpper)) const onlyFirstToUpper = S.compose(toFirstUpper)(S.toLower) @@ -17,4 +26,4 @@ const startCase = R.compose( splitOnUpper ) -export { startCase, onlyFirstToUpper } +export { startCase, onlyFirstToUpper, formatLong }