fix: rework wallet screen
This commit is contained in:
parent
1f7ae74b42
commit
1f6d272aa0
103 changed files with 2094 additions and 3892 deletions
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -17,7 +17,7 @@ const ACCOUNT_LIST = [
|
||||||
{ code: 'bitstamp', display: 'Bitstamp', class: TICKER, cryptos: [BTC, ETH, LTC, BCH] },
|
{ code: 'bitstamp', display: 'Bitstamp', class: TICKER, cryptos: [BTC, ETH, LTC, BCH] },
|
||||||
{ code: 'coinbase', display: 'Coinbase', 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: '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: 'bitcoind', display: 'bitcoind', class: WALLET, cryptos: [BTC] },
|
||||||
{ code: 'no-layer2', display: 'No Layer 2', class: LAYER_2, cryptos: ALL_CRYPTOS },
|
{ code: 'no-layer2', display: 'No Layer 2', class: LAYER_2, cryptos: ALL_CRYPTOS },
|
||||||
{ code: 'infura', display: 'Infura', class: WALLET, cryptos: [ETH] },
|
{ 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: 'bitstamp', display: 'Bitstamp', class: EXCHANGE, cryptos: [BTC, ETH, LTC, BCH] },
|
||||||
{ code: 'itbit', display: 'itBit', class: EXCHANGE, cryptos: [BTC] },
|
{ code: 'itbit', display: 'itBit', class: EXCHANGE, cryptos: [BTC] },
|
||||||
{ code: 'kraken', display: 'Kraken', class: EXCHANGE, cryptos: [BTC, ETH, LTC, DASH, ZEC, BCH] },
|
{ 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: 'no-exchange', display: 'No exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS },
|
||||||
{ code: 'mock-exchange', display: 'Mock exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS },
|
{ code: 'mock-exchange', display: 'Mock exchange', class: EXCHANGE, cryptos: ALL_CRYPTOS, dev: true },
|
||||||
{ code: 'mock-sms', display: 'Mock SMS', class: SMS },
|
{ code: 'mock-sms', display: 'Mock SMS', class: SMS, dev: true },
|
||||||
{ code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER },
|
{ code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER, dev: true },
|
||||||
{ code: 'twilio', display: 'Twilio', class: SMS },
|
{ code: 'twilio', display: 'Twilio', class: SMS },
|
||||||
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
||||||
{ code: 'all-zero-conf', display: 'Always 0-conf', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH] },
|
{ 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: 'no-zero-conf', display: 'Always 1-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS },
|
||||||
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
|
{ 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 }
|
module.exports = { ACCOUNT_LIST }
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const funding = require('../funding')
|
||||||
const supervisor = require('../supervisor')
|
const supervisor = require('../supervisor')
|
||||||
const serverLogs = require('../server-logs')
|
const serverLogs = require('../server-logs')
|
||||||
const pairing = require('../pairing')
|
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?
|
// TODO why does server logs messages can be null?
|
||||||
const typeDefs = gql`
|
const typeDefs = gql`
|
||||||
|
|
@ -61,7 +61,7 @@ const typeDefs = gql`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Customer {
|
type Customer {
|
||||||
name: String!
|
name: String
|
||||||
phone: String
|
phone: String
|
||||||
totalTxs: Int
|
totalTxs: Int
|
||||||
totalSpent: String
|
totalSpent: String
|
||||||
|
|
@ -71,7 +71,7 @@ const typeDefs = gql`
|
||||||
lastTxClass: String
|
lastTxClass: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Account {
|
type AccountConfig {
|
||||||
code: String!
|
code: String!
|
||||||
display: String!
|
display: String!
|
||||||
class: String!
|
class: String!
|
||||||
|
|
@ -156,7 +156,7 @@ const typeDefs = gql`
|
||||||
countries: [Country]
|
countries: [Country]
|
||||||
currencies: [Currency]
|
currencies: [Currency]
|
||||||
languages: [Language]
|
languages: [Language]
|
||||||
accounts: [Account]
|
accountsConfig: [AccountConfig]
|
||||||
cryptoCurrencies: [CryptoCurrency]
|
cryptoCurrencies: [CryptoCurrency]
|
||||||
machines: [Machine]
|
machines: [Machine]
|
||||||
customers: [Customer]
|
customers: [Customer]
|
||||||
|
|
@ -166,6 +166,7 @@ const typeDefs = gql`
|
||||||
uptime: [ProcessStatus]
|
uptime: [ProcessStatus]
|
||||||
serverLogs: [ServerLog]
|
serverLogs: [ServerLog]
|
||||||
transactions: [Transaction]
|
transactions: [Transaction]
|
||||||
|
accounts: [JSONObject]
|
||||||
config: JSONObject
|
config: JSONObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,6 +189,8 @@ const typeDefs = gql`
|
||||||
serverSupportLogs: SupportLogsResponse
|
serverSupportLogs: SupportLogsResponse
|
||||||
saveConfig(config: JSONObject): JSONObject
|
saveConfig(config: JSONObject): JSONObject
|
||||||
createPairingTotem(name: String!): String
|
createPairingTotem(name: String!): String
|
||||||
|
saveAccount(account: JSONObject): [JSONObject]
|
||||||
|
saveAccounts(accounts: [JSONObject]): [JSONObject]
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
@ -202,7 +205,7 @@ const resolvers = {
|
||||||
countries: () => countries,
|
countries: () => countries,
|
||||||
currencies: () => currencies,
|
currencies: () => currencies,
|
||||||
languages: () => languages,
|
languages: () => languages,
|
||||||
accounts: () => accounts,
|
accountsConfig: () => accountsConfig,
|
||||||
cryptoCurrencies: () => coins,
|
cryptoCurrencies: () => coins,
|
||||||
machines: () => machineLoader.getMachineNames(),
|
machines: () => machineLoader.getMachineNames(),
|
||||||
customers: () => customers.getCustomersList(),
|
customers: () => customers.getCustomersList(),
|
||||||
|
|
@ -212,13 +215,16 @@ const resolvers = {
|
||||||
uptime: () => supervisor.getAllProcessInfo(),
|
uptime: () => supervisor.getAllProcessInfo(),
|
||||||
serverLogs: () => serverLogs.getServerLogs(),
|
serverLogs: () => serverLogs.getServerLogs(),
|
||||||
transactions: () => transactions.batch(),
|
transactions: () => transactions.batch(),
|
||||||
config: () => settingsLoader.getConfig()
|
config: () => settingsLoader.getConfig(),
|
||||||
|
accounts: () => settingsLoader.getAccounts()
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
machineAction: (...[, { deviceId, action }]) => machineAction({ deviceId, action }),
|
machineAction: (...[, { deviceId, action }]) => machineAction({ deviceId, action }),
|
||||||
machineSupportLogs: (...[, { deviceId }]) => supportLogs.insert(deviceId),
|
machineSupportLogs: (...[, { deviceId }]) => supportLogs.insert(deviceId),
|
||||||
createPairingTotem: (...[, { name }]) => pairing.totem(name),
|
createPairingTotem: (...[, { name }]) => pairing.totem(name),
|
||||||
serverSupportLogs: () => serverLogs.insert(),
|
serverSupportLogs: () => serverLogs.insert(),
|
||||||
|
saveAccount: (...[, { account }]) => settingsLoader.saveAccounts([account]),
|
||||||
|
saveAccounts: (...[, { accounts }]) => settingsLoader.saveAccounts(accounts),
|
||||||
saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config)
|
saveConfig: (...[, { config }]) => settingsLoader.saveConfig(config)
|
||||||
.then(it => {
|
.then(it => {
|
||||||
notify()
|
notify()
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
const machineLoader = require('../machine-loader')
|
const machineLoader = require('../machine-loader')
|
||||||
const { UserInputError } = require('apollo-server-express')
|
const { UserInputError } = require('apollo-server-express')
|
||||||
|
|
||||||
function getMachine(machineId) {
|
function getMachine (machineId) {
|
||||||
return machineLoader.getMachines()
|
return machineLoader.getMachines()
|
||||||
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
|
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
|
||||||
}
|
}
|
||||||
|
|
||||||
function machineAction({ deviceId, action }) {
|
function machineAction ({ deviceId, action }) {
|
||||||
|
return getMachine(deviceId)
|
||||||
return getMachine(deviceId)
|
.then(machine => {
|
||||||
.then(machine => {
|
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
|
||||||
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
|
return machine
|
||||||
return machine
|
})
|
||||||
})
|
.then(machineLoader.setMachine({ deviceId, action }))
|
||||||
.then(machineLoader.setMachine({ deviceId, action }))
|
.then(getMachine(deviceId))
|
||||||
.then(getMachine(deviceId))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { machineAction }
|
module.exports = { machineAction }
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,46 @@ low(adapter).then(it => {
|
||||||
db = it
|
db = it
|
||||||
})
|
})
|
||||||
|
|
||||||
function saveConfig (config) {
|
function replace (array, index, value) {
|
||||||
const currentState = db.getState()
|
return array.slice(0, index).concat([value]).concat(array.slice(index + 1))
|
||||||
// TODO this should be _.assign
|
}
|
||||||
// change after flattening of schema
|
|
||||||
const newState = _.mergeWith((objValue, srcValue) => {
|
|
||||||
if (_.isArray(objValue)) {
|
|
||||||
return srcValue
|
|
||||||
}
|
|
||||||
}, currentState, config)
|
|
||||||
|
|
||||||
|
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)
|
db.setState(newState)
|
||||||
return db.write()
|
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 () {
|
function getConfig () {
|
||||||
return db.getState()
|
const state = db.getState()
|
||||||
|
return (state && state.config) || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { getConfig, saveConfig }
|
module.exports = { getConfig, saveConfig, saveAccounts, getAccounts }
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import extendJss from 'jss-plugin-extend'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { BrowserRouter as Router } from 'react-router-dom'
|
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 { tree, Routes } from './routing/routes'
|
||||||
import global from './styling/global'
|
import global from './styling/global'
|
||||||
import theme from './styling/theme'
|
import theme from './styling/theme'
|
||||||
|
|
|
||||||
|
|
@ -191,8 +191,8 @@ const LogsDownloaderPopover = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
const radioButtonOptions = [
|
const radioButtonOptions = [
|
||||||
{ label: 'All logs', value: radioButtonAll },
|
{ display: 'All logs', code: radioButtonAll },
|
||||||
{ label: 'Date range', value: radioButtonRange }
|
{ display: 'Date range', code: radioButtonRange }
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { makeStyles, Modal as MaterialModal, Paper } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React from 'react'
|
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'
|
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
|
|
@ -10,42 +12,68 @@ const styles = {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
modalContentWrapper: {
|
wrapper: ({ width }) => ({
|
||||||
|
width,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
position: 'relative',
|
flexDirection: 'column',
|
||||||
minHeight: 400,
|
minHeight: 400,
|
||||||
maxHeight: '90vh',
|
maxHeight: '90vh',
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
outline: 0,
|
outline: 0
|
||||||
'& > div': {
|
}),
|
||||||
width: '100%'
|
content: {
|
||||||
}
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flex: 1,
|
||||||
|
padding: [[0, 32]]
|
||||||
},
|
},
|
||||||
closeIcon: {
|
button: {
|
||||||
position: 'absolute',
|
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
padding: 0,
|
padding: 0,
|
||||||
top: 20,
|
margin: [[20, 20, 'auto', 'auto']]
|
||||||
right: 20
|
},
|
||||||
|
header: {
|
||||||
|
display: 'flex'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
margin: [[28, 0, 8, 32]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const Modal = ({ handleClose, children, className, ...props }) => {
|
const Modal = ({
|
||||||
const classes = useStyles()
|
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 (
|
return (
|
||||||
<MaterialModal onClose={handleClose} className={classes.modal} {...props}>
|
<MaterialModal onClose={innerClose} className={classes.modal} {...props}>
|
||||||
<Paper className={classnames(classes.modalContentWrapper, className)}>
|
<Paper className={classnames(classes.wrapper, className)}>
|
||||||
<button
|
<div className={classes.header}>
|
||||||
className={classnames(classes.iconButton, classes.closeIcon)}
|
{title && <H1 className={classes.title}>{title}</H1>}
|
||||||
onClick={() => handleClose()}>
|
<IconButton
|
||||||
<CloseIcon />
|
size={20}
|
||||||
</button>
|
className={classes.button}
|
||||||
{children}
|
onClick={() => handleClose()}>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<div className={classes.content}>{children}</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
</MaterialModal>
|
</MaterialModal>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,10 @@ const styles = {
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const Stage = memo(({ stages, currentStage, color = 'spring', className }) => {
|
const Stepper = memo(({ steps, currentStep, color = 'spring', className }) => {
|
||||||
if (currentStage < 1 || currentStage > stages)
|
if (currentStep < 1 || currentStep > steps)
|
||||||
throw Error('Value of currentStage is invalid')
|
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()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
|
@ -80,7 +80,7 @@ const Stage = memo(({ stages, currentStage, color = 'spring', className }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames(className, classes.stages)}>
|
<div className={classnames(className, classes.stages)}>
|
||||||
{R.range(1, currentStage).map(idx => (
|
{R.range(1, currentStep).map(idx => (
|
||||||
<div key={idx} className={classes.wrapper}>
|
<div key={idx} className={classes.wrapper}>
|
||||||
{idx > 1 && <div className={classnames(separatorClasses)} />}
|
{idx > 1 && <div className={classnames(separatorClasses)} />}
|
||||||
<div className={classes.stage}>
|
<div className={classes.stage}>
|
||||||
|
|
@ -90,13 +90,13 @@ const Stage = memo(({ stages, currentStage, color = 'spring', className }) => {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className={classes.wrapper}>
|
<div className={classes.wrapper}>
|
||||||
{currentStage > 1 && <div className={classnames(separatorClasses)} />}
|
{currentStep > 1 && <div className={classnames(separatorClasses)} />}
|
||||||
<div className={classes.stage}>
|
<div className={classes.stage}>
|
||||||
{color === 'spring' && <CurrentStageIconSpring />}
|
{color === 'spring' && <CurrentStageIconSpring />}
|
||||||
{color === 'zodiac' && <CurrentStageIconZodiac />}
|
{color === 'zodiac' && <CurrentStageIconZodiac />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{R.range(currentStage + 1, stages + 1).map(idx => (
|
{R.range(currentStep + 1, steps + 1).map(idx => (
|
||||||
<div key={idx} className={classes.wrapper}>
|
<div key={idx} className={classes.wrapper}>
|
||||||
<div className={classnames(separatorEmptyClasses)} />
|
<div className={classnames(separatorEmptyClasses)} />
|
||||||
<div className={classes.stage}>
|
<div className={classes.stage}>
|
||||||
|
|
@ -109,4 +109,4 @@ const Stage = memo(({ stages, currentStage, color = 'spring', className }) => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export default Stage
|
export default Stepper
|
||||||
|
|
@ -44,8 +44,8 @@ const BooleanPropertiesTable = memo(
|
||||||
}
|
}
|
||||||
|
|
||||||
const radioButtonOptions = [
|
const radioButtonOptions = [
|
||||||
{ label: 'Yes', value: true },
|
{ display: 'Yes', code: true },
|
||||||
{ label: 'No', value: false }
|
{ display: 'No', code: false }
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!elements || radioGroupValues?.length === 0) return null
|
if (!elements || radioGroupValues?.length === 0) return null
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ const useStyles = makeStyles(styles)
|
||||||
const ActionButton = memo(({ size = 'lg', children, className, ...props }) => {
|
const ActionButton = memo(({ size = 'lg', children, className, ...props }) => {
|
||||||
const classes = useStyles({ size })
|
const classes = useStyles({ size })
|
||||||
return (
|
return (
|
||||||
<button className={classnames(classes.button, className)} {...props}>
|
<div className={classnames(className, classes.wrapper)}>
|
||||||
{children}
|
<button className={classes.button} {...props}>
|
||||||
</button>
|
{children}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
import {
|
import {
|
||||||
white,
|
white,
|
||||||
disabledColor,
|
disabledColor,
|
||||||
|
|
@ -6,7 +7,6 @@ import {
|
||||||
secondaryColorDarker,
|
secondaryColorDarker,
|
||||||
spacer
|
spacer
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
import typographyStyles from 'src/components/typography/styles'
|
|
||||||
|
|
||||||
const { h3 } = typographyStyles
|
const { h3 } = typographyStyles
|
||||||
|
|
||||||
|
|
@ -21,6 +21,11 @@ const pickSize = size => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
wrapper: ({ size }) => {
|
||||||
|
const height = pickSize(size)
|
||||||
|
const shadowSize = height / 12
|
||||||
|
return { height: height + shadowSize / 2 }
|
||||||
|
},
|
||||||
button: ({ size }) => {
|
button: ({ size }) => {
|
||||||
const height = pickSize(size)
|
const height = pickSize(size)
|
||||||
const shadowSize = height / 12
|
const shadowSize = height / 12
|
||||||
|
|
|
||||||
|
|
@ -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'
|
import React from 'react'
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
|
label: ({ size }) => ({
|
||||||
|
width: size,
|
||||||
|
height: size
|
||||||
|
}),
|
||||||
root: {
|
root: {
|
||||||
|
'&svg': {
|
||||||
|
viewbox: null
|
||||||
|
},
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
backgroundColor: 'inherit'
|
backgroundColor: 'inherit'
|
||||||
}
|
}
|
||||||
|
|
@ -11,15 +18,16 @@ const styles = {
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const IconButton = ({ children, onClick, ...props }) => {
|
const IconButton = ({ size, children, onClick, ...props }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles({ size })
|
||||||
return (
|
return (
|
||||||
<IconB
|
<IconB
|
||||||
{...props}
|
{...props}
|
||||||
classes={{ root: classes.root }}
|
size="small"
|
||||||
|
classes={{ root: classes.root, label: classes.label }}
|
||||||
disableRipple
|
disableRipple
|
||||||
onClick={onClick}>
|
onClick={onClick}>
|
||||||
<SvgIcon>{children}</SvgIcon>
|
{children}
|
||||||
</IconB>
|
</IconB>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<div>
|
|
||||||
<THead>
|
|
||||||
{elements.map(
|
|
||||||
({ width, size, className, textAlign, header }, idx) => (
|
|
||||||
<Th
|
|
||||||
key={idx}
|
|
||||||
size={size}
|
|
||||||
width={width}
|
|
||||||
className={className}
|
|
||||||
textAlign={textAlign}>
|
|
||||||
{header}
|
|
||||||
</Th>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</THead>
|
|
||||||
</div>
|
|
||||||
<div style={{ flex: '1 1 auto' }}>
|
|
||||||
<AutoSizer disableWidth>
|
|
||||||
{({ height }) => (
|
|
||||||
<List
|
|
||||||
height={height}
|
|
||||||
width={mainWidth}
|
|
||||||
rowCount={data.length}
|
|
||||||
rowHeight={cache.rowHeight}
|
|
||||||
rowRenderer={({ index, isScrolling, key, parent, style }) => (
|
|
||||||
<CellMeasurer
|
|
||||||
cache={cache}
|
|
||||||
columnIndex={0}
|
|
||||||
key={key}
|
|
||||||
parent={parent}
|
|
||||||
rowIndex={index}>
|
|
||||||
<div style={style}>
|
|
||||||
<Tr
|
|
||||||
error={data[index].error}
|
|
||||||
errorMessage={data[index].errorMessage}>
|
|
||||||
{elements.map(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
size,
|
|
||||||
width,
|
|
||||||
className,
|
|
||||||
textAlign,
|
|
||||||
view = it => it?.toString()
|
|
||||||
},
|
|
||||||
idx
|
|
||||||
) => (
|
|
||||||
<Td
|
|
||||||
key={idx}
|
|
||||||
size={size}
|
|
||||||
width={width}
|
|
||||||
className={className}
|
|
||||||
textAlign={textAlign}>
|
|
||||||
{view(data[index])}
|
|
||||||
</Td>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Tr>
|
|
||||||
</div>
|
|
||||||
</CellMeasurer>
|
|
||||||
)}
|
|
||||||
overscanRowCount={50}
|
|
||||||
deferredMeasurementCache={cache}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</AutoSizer>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default DataTable
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import DataTable from './DataTable'
|
|
||||||
|
|
||||||
export { DataTable }
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export default React.createContext()
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
import React from 'react'
|
import React, { useContext } from 'react'
|
||||||
|
|
||||||
import { Td, THead } from 'src/components/fake-table/Table'
|
import { Td, THead } from 'src/components/fake-table/Table'
|
||||||
import { startCase } from 'src/utils/string'
|
import { startCase } from 'src/utils/string'
|
||||||
|
|
||||||
import { ACTION_COL_SIZE, DEFAULT_COL_SIZE } from './consts'
|
import TableCtx from './Context'
|
||||||
|
|
||||||
const Header = ({ elements, enableEdit, enableDelete }) => {
|
|
||||||
const actionColSize =
|
|
||||||
enableDelete && enableEdit ? ACTION_COL_SIZE / 2 : ACTION_COL_SIZE
|
|
||||||
|
|
||||||
|
const Header = () => {
|
||||||
|
const {
|
||||||
|
elements,
|
||||||
|
enableEdit,
|
||||||
|
editWidth,
|
||||||
|
enableDelete,
|
||||||
|
deleteWidth,
|
||||||
|
enableToggle,
|
||||||
|
toggleWidth,
|
||||||
|
DEFAULT_COL_SIZE
|
||||||
|
} = useContext(TableCtx)
|
||||||
return (
|
return (
|
||||||
<THead>
|
<THead>
|
||||||
{elements.map(
|
{elements.map(
|
||||||
|
|
@ -19,15 +26,20 @@ const Header = ({ elements, enableEdit, enableDelete }) => {
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
{enableEdit && (
|
{enableEdit && (
|
||||||
<Td header width={actionColSize} textAlign="right">
|
<Td header width={editWidth} textAlign="center">
|
||||||
Edit
|
Edit
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
{enableDelete && (
|
{enableDelete && (
|
||||||
<Td header width={actionColSize} textAlign="right">
|
<Td header width={deleteWidth} textAlign="center">
|
||||||
Delete
|
Delete
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
{enableToggle && (
|
||||||
|
<Td header width={toggleWidth} textAlign="center">
|
||||||
|
Enable
|
||||||
|
</Td>
|
||||||
|
)}
|
||||||
</THead>
|
</THead>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
<EditableTable name={name} data={innerData} save={innerSave} {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NamespacedTable
|
||||||
|
|
@ -1,39 +1,46 @@
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
|
||||||
import { Field, useFormikContext } from 'formik'
|
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 { Link, IconButton } from 'src/components/buttons'
|
||||||
import { Td, Tr } from 'src/components/fake-table/Table'
|
import { Td, Tr } from 'src/components/fake-table/Table'
|
||||||
|
import { Switch } from 'src/components/inputs'
|
||||||
import { TL2 } from 'src/components/typography'
|
import { TL2 } from 'src/components/typography'
|
||||||
import { ReactComponent as DisabledDeleteIcon } from 'src/styling/icons/action/delete/disabled.svg'
|
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 DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
|
||||||
import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.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 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 styles from './Row.styles'
|
||||||
import { ACTION_COL_SIZE } from './consts'
|
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const ActionCol = ({
|
const ActionCol = ({ disabled, editing }) => {
|
||||||
editing,
|
|
||||||
setEditing,
|
|
||||||
enableEdit,
|
|
||||||
disabled,
|
|
||||||
onDelete,
|
|
||||||
enableDelete
|
|
||||||
}) => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { values, submitForm, resetForm } = useFormikContext()
|
const { values, submitForm, resetForm } = useFormikContext()
|
||||||
|
const {
|
||||||
|
editWidth,
|
||||||
|
onEdit,
|
||||||
|
enableEdit,
|
||||||
|
enableDelete,
|
||||||
|
disableRowEdit,
|
||||||
|
onDelete,
|
||||||
|
deleteWidth,
|
||||||
|
enableToggle,
|
||||||
|
onToggle,
|
||||||
|
toggleWidth,
|
||||||
|
actionColSize
|
||||||
|
} = useContext(TableCtx)
|
||||||
|
|
||||||
const actionColSize =
|
const disableEdit = disabled || (disableRowEdit && disableRowEdit(values))
|
||||||
enableDelete && enableEdit ? ACTION_COL_SIZE / 2 : ACTION_COL_SIZE
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editing && (
|
{editing && (
|
||||||
<Td textAlign="center" width={ACTION_COL_SIZE}>
|
<Td textAlign="center" width={actionColSize}>
|
||||||
<Link
|
<Link
|
||||||
className={classes.cancelButton}
|
className={classes.cancelButton}
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|
@ -46,22 +53,32 @@ const ActionCol = ({
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
{!editing && enableEdit && (
|
{!editing && enableEdit && (
|
||||||
<Td textAlign="right" width={actionColSize}>
|
<Td textAlign="center" width={editWidth}>
|
||||||
<IconButton
|
<IconButton
|
||||||
disabled={disabled}
|
disabled={disableEdit}
|
||||||
className={classes.editButton}
|
className={classes.editButton}
|
||||||
onClick={() => setEditing && setEditing(values.id)}>
|
onClick={() => onEdit && onEdit(values.id)}>
|
||||||
{disabled ? <DisabledEditIcon /> : <EditIcon />}
|
{disableEdit ? <DisabledEditIcon /> : <EditIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
{!editing && enableDelete && (
|
{!editing && enableDelete && (
|
||||||
<Td textAlign="right" width={actionColSize}>
|
<Td textAlign="center" width={deleteWidth}>
|
||||||
<IconButton disabled={disabled} onClick={() => onDelete(values.id)}>
|
<IconButton disabled={disabled} onClick={() => onDelete(values.id)}>
|
||||||
{disabled ? <DisabledDeleteIcon /> : <DeleteIcon />}
|
{disabled ? <DisabledDeleteIcon /> : <DeleteIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
|
{!editing && enableToggle && (
|
||||||
|
<Td textAlign="center" width={toggleWidth}>
|
||||||
|
<Switch
|
||||||
|
checked={!!values.active}
|
||||||
|
value={!!values.active}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={() => onToggle(values.id)}
|
||||||
|
/>
|
||||||
|
</Td>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +87,7 @@ const ECol = ({ editing, config }) => {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
input,
|
input,
|
||||||
|
editable = true,
|
||||||
size,
|
size,
|
||||||
bold,
|
bold,
|
||||||
width,
|
width,
|
||||||
|
|
@ -82,11 +100,6 @@ const ECol = ({ editing, config }) => {
|
||||||
const { values } = useFormikContext()
|
const { values } = useFormikContext()
|
||||||
const classes = useStyles({ textAlign, size })
|
const classes = useStyles({ textAlign, size })
|
||||||
|
|
||||||
const viewClasses = {
|
|
||||||
[classes.bold]: bold,
|
|
||||||
[classes.size]: true
|
|
||||||
}
|
|
||||||
|
|
||||||
const iProps = {
|
const iProps = {
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
size,
|
size,
|
||||||
|
|
@ -105,43 +118,58 @@ const ECol = ({ editing, config }) => {
|
||||||
className={{ [classes.withSuffix]: suffix }}
|
className={{ [classes.withSuffix]: suffix }}
|
||||||
width={width}
|
width={width}
|
||||||
size={size}
|
size={size}
|
||||||
|
bold={bold}
|
||||||
textAlign={textAlign}>
|
textAlign={textAlign}>
|
||||||
{editing && <Field name={name} component={input} {...iProps} />}
|
{editing && editable ? (
|
||||||
{!editing && values && (
|
<Field name={name} component={input} {...iProps} />
|
||||||
<div className={classnames(viewClasses)}>{view(values[name])}</div>
|
) : (
|
||||||
|
values && <>{view(values[name])}</>
|
||||||
)}
|
)}
|
||||||
{suffix && <TL2 className={classes.suffix}>{suffix}</TL2>}
|
{suffix && <TL2 className={classes.suffix}>{suffix}</TL2>}
|
||||||
</Td>
|
</Td>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ERow = ({
|
const groupStriped = elements => {
|
||||||
elements,
|
const [toStripe, noStripe] = R.partition(R.has('stripe'))(elements)
|
||||||
enableEdit,
|
|
||||||
enableDelete,
|
|
||||||
onDelete,
|
|
||||||
editing,
|
|
||||||
setEditing,
|
|
||||||
disabled
|
|
||||||
}) => {
|
|
||||||
const { errors } = useFormikContext()
|
|
||||||
|
|
||||||
|
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: () => <StripesSvg /> },
|
||||||
|
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 (
|
return (
|
||||||
<Tr
|
<Tr
|
||||||
error={errors && errors.length}
|
error={errors && errors.length}
|
||||||
errorMessage={errors && errors.toString()}>
|
errorMessage={errors && errors.toString()}>
|
||||||
{elements.map((it, idx) => (
|
{iElements.map((it, idx) => {
|
||||||
<ECol key={idx} config={it} editing={editing} />
|
return <ECol key={idx} config={it} editing={editing} />
|
||||||
))}
|
})}
|
||||||
{(enableEdit || enableDelete) && (
|
{(enableEdit || enableDelete || enableToggle) && (
|
||||||
<ActionCol
|
<ActionCol disabled={disabled} editing={editing} />
|
||||||
disabled={disabled}
|
|
||||||
editing={editing}
|
|
||||||
setEditing={setEditing}
|
|
||||||
onDelete={onDelete}
|
|
||||||
enableEdit={enableEdit}
|
|
||||||
enableDelete={enableDelete}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Tr>
|
</Tr>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,15 @@ import { v4 } from 'uuid'
|
||||||
import Link from 'src/components/buttons/Link.js'
|
import Link from 'src/components/buttons/Link.js'
|
||||||
import { AddButton } from 'src/components/buttons/index.js'
|
import { AddButton } from 'src/components/buttons/index.js'
|
||||||
import { TBody, Table } from 'src/components/fake-table/Table'
|
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 Header from './Header'
|
||||||
import ERow from './Row'
|
import ERow from './Row'
|
||||||
import styles from './Table.styles'
|
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)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
|
@ -24,17 +27,25 @@ const getWidth = R.compose(
|
||||||
const ETable = ({
|
const ETable = ({
|
||||||
name,
|
name,
|
||||||
title,
|
title,
|
||||||
|
titleLg,
|
||||||
elements = [],
|
elements = [],
|
||||||
data = [],
|
data = [],
|
||||||
save,
|
save,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
enableCreate,
|
enableCreate,
|
||||||
|
enableEdit,
|
||||||
|
editWidth: outerEditWidth,
|
||||||
|
enableDelete,
|
||||||
|
deleteWidth = ACTION_COL_SIZE,
|
||||||
|
enableToggle,
|
||||||
|
toggleWidth = ACTION_COL_SIZE,
|
||||||
|
onToggle,
|
||||||
forceDisable,
|
forceDisable,
|
||||||
disableAdd,
|
disableAdd,
|
||||||
enableDelete,
|
|
||||||
initialValues,
|
initialValues,
|
||||||
enableEdit,
|
|
||||||
setEditing,
|
setEditing,
|
||||||
|
stripeWhen,
|
||||||
|
disableRowEdit,
|
||||||
createText = 'Add override'
|
createText = 'Add override'
|
||||||
}) => {
|
}) => {
|
||||||
const [editingId, setEditingId] = useState(null)
|
const [editingId, setEditingId] = useState(null)
|
||||||
|
|
@ -45,7 +56,7 @@ const ETable = ({
|
||||||
const list = index !== -1 ? R.update(index, it, data) : R.prepend(it, data)
|
const list = index !== -1 ? R.update(index, it, data) : R.prepend(it, data)
|
||||||
|
|
||||||
// no response means the save failed
|
// no response means the save failed
|
||||||
const response = await save({ [name]: list })
|
const response = await save({ [name]: list }, it)
|
||||||
if (!response) return
|
if (!response) return
|
||||||
setAdding(false)
|
setAdding(false)
|
||||||
setEditingId(null)
|
setEditingId(null)
|
||||||
|
|
@ -69,83 +80,103 @@ const ETable = ({
|
||||||
|
|
||||||
const addField = () => setAdding(true)
|
const addField = () => setAdding(true)
|
||||||
|
|
||||||
const actionSize = enableEdit || enableDelete ? ACTION_COL_SIZE : 0
|
const widthIfEditNull =
|
||||||
const width = getWidth(elements) + actionSize
|
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 classes = useStyles({ width })
|
||||||
|
|
||||||
const showButtonOnEmpty = !data.length && enableCreate && !adding
|
const showButtonOnEmpty = !data.length && enableCreate && !adding
|
||||||
const canAdd = !forceDisable && !editingId && !disableAdd && !adding
|
const canAdd = !forceDisable && !editingId && !disableAdd && !adding
|
||||||
const showTable = adding || data.length !== 0
|
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 (
|
return (
|
||||||
<div className={classes.wrapper}>
|
<TableCtx.Provider value={ctxValue}>
|
||||||
{showButtonOnEmpty && (
|
<div className={classes.wrapper}>
|
||||||
<AddButton disabled={!canAdd} onClick={addField}>
|
{showButtonOnEmpty && (
|
||||||
{createText}
|
<AddButton disabled={!canAdd} onClick={addField}>
|
||||||
</AddButton>
|
{createText}
|
||||||
)}
|
</AddButton>
|
||||||
{showTable && (
|
)}
|
||||||
<>
|
{showTable && (
|
||||||
<div className={classes.outerHeader}>
|
<>
|
||||||
{title && <Info2 className={classes.title}>{title}</Info2>}
|
{(title || enableCreate) && (
|
||||||
{enableCreate && canAdd && (
|
<div className={classes.outerHeader}>
|
||||||
<Link className={classes.addLink} onClick={addField}>
|
{title && titleLg && (
|
||||||
{createText}
|
<TL1 className={classes.title}>{title}</TL1>
|
||||||
</Link>
|
)}
|
||||||
|
{title && !titleLg && (
|
||||||
|
<Info2 className={classes.title}>{title}</Info2>
|
||||||
|
)}
|
||||||
|
{enableCreate && canAdd && (
|
||||||
|
<Link className={classes.addLink} onClick={addField}>
|
||||||
|
{createText}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
<Table>
|
||||||
<Table>
|
<Header />
|
||||||
<Header
|
<TBody>
|
||||||
elements={elements}
|
{adding && (
|
||||||
enableEdit={enableEdit}
|
<Formik
|
||||||
enableDelete={enableDelete}
|
initialValues={{ id: v4(), ...initialValues }}
|
||||||
/>
|
onReset={onReset}
|
||||||
<TBody>
|
validationSchema={validationSchema}
|
||||||
{adding && (
|
onSubmit={innerSave}>
|
||||||
<Formik
|
<Form>
|
||||||
initialValues={{ id: v4(), ...initialValues }}
|
<ERow editing={true} disabled={forceDisable} />
|
||||||
onReset={onReset}
|
</Form>
|
||||||
validationSchema={validationSchema}
|
</Formik>
|
||||||
onSubmit={innerSave}>
|
)}
|
||||||
<Form>
|
{data.map((it, idx) => (
|
||||||
<ERow
|
<Formik
|
||||||
editing={true}
|
key={it.id ?? idx}
|
||||||
disabled={forceDisable}
|
enableReinitialize
|
||||||
enableEdit={enableEdit}
|
initialValues={it}
|
||||||
enableDelete={enableDelete}
|
onReset={onReset}
|
||||||
elements={elements}
|
validationSchema={validationSchema}
|
||||||
/>
|
onSubmit={innerSave}>
|
||||||
</Form>
|
<Form>
|
||||||
</Formik>
|
<ERow
|
||||||
)}
|
editing={editingId === it.id}
|
||||||
{data.map((it, idx) => (
|
disabled={
|
||||||
<Formik
|
forceDisable || (editingId && editingId !== it.id)
|
||||||
key={it.id ?? idx}
|
}
|
||||||
enableReinitialize
|
/>
|
||||||
initialValues={it}
|
</Form>
|
||||||
onReset={onReset}
|
</Formik>
|
||||||
validationSchema={validationSchema}
|
))}
|
||||||
onSubmit={innerSave}>
|
</TBody>
|
||||||
<Form>
|
</Table>
|
||||||
<ERow
|
</>
|
||||||
editing={editingId === it.id}
|
)}
|
||||||
disabled={
|
</div>
|
||||||
forceDisable || (editingId && editingId !== it.id)
|
</TableCtx.Provider>
|
||||||
}
|
|
||||||
setEditing={onEdit}
|
|
||||||
onDelete={onDelete}
|
|
||||||
enableEdit={enableEdit}
|
|
||||||
enableDelete={enableDelete}
|
|
||||||
elements={elements}
|
|
||||||
/>
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
))}
|
|
||||||
</TBody>
|
|
||||||
</Table>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
const ACTION_COL_SIZE = 175
|
|
||||||
const DEFAULT_COL_SIZE = 100
|
|
||||||
|
|
||||||
export { ACTION_COL_SIZE, DEFAULT_COL_SIZE }
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import NamespacedTable from './NamespacedTable'
|
||||||
import Table from './Table'
|
import Table from './Table'
|
||||||
|
|
||||||
export { Table }
|
export { Table, NamespacedTable }
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,7 @@ const TDoubleLevelHead = ({ children, className }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TBody = ({ children, className }) => {
|
const TBody = ({ children, className }) => {
|
||||||
const classes = useStyles()
|
return <div className={classnames(className)}>{children}</div>
|
||||||
return <div className={classnames(className, classes.body)}>{children}</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Td = ({
|
const Td = ({
|
||||||
|
|
@ -42,16 +41,17 @@ const Td = ({
|
||||||
className,
|
className,
|
||||||
width = 100,
|
width = 100,
|
||||||
size,
|
size,
|
||||||
|
bold,
|
||||||
textAlign,
|
textAlign,
|
||||||
action
|
action
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles({ textAlign, width })
|
const classes = useStyles({ textAlign, width, size })
|
||||||
const classNames = {
|
const classNames = {
|
||||||
[classes.td]: true,
|
[classes.td]: true,
|
||||||
[classes.tdHeader]: header,
|
[classes.tdHeader]: header,
|
||||||
[classes.actionCol]: action,
|
[classes.actionCol]: action,
|
||||||
[classes.large]: size === 'lg' && !header,
|
[classes.size]: !header,
|
||||||
[classes.md]: size === 'md' && !header
|
[classes.bold]: !header && bold
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={classnames(className, classNames)}>{children}</div>
|
return <div className={classnames(className, classNames)}>{children}</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import typographyStyles from 'src/components/typography/styles'
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
|
import { bySize, bold } from 'src/styling/helpers'
|
||||||
import {
|
import {
|
||||||
tableHeaderColor,
|
tableHeaderColor,
|
||||||
tableHeaderHeight,
|
tableHeaderHeight,
|
||||||
|
|
@ -9,18 +10,11 @@ import {
|
||||||
offColor
|
offColor
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
const { tl1, info2, tl2, p, label1 } = typographyStyles
|
const { tl2, p, label1 } = typographyStyles
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
body: {
|
size: ({ size }) => bySize(size),
|
||||||
borderSpacing: [[0, 4]]
|
bold,
|
||||||
},
|
|
||||||
large: {
|
|
||||||
extend: tl1
|
|
||||||
},
|
|
||||||
md: {
|
|
||||||
extend: info2
|
|
||||||
},
|
|
||||||
header: {
|
header: {
|
||||||
extend: tl2,
|
extend: tl2,
|
||||||
backgroundColor: tableHeaderColor,
|
backgroundColor: tableHeaderColor,
|
||||||
|
|
@ -79,7 +73,7 @@ export default {
|
||||||
mainContent: {
|
mainContent: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
minHeight: 54
|
minHeight: 48
|
||||||
},
|
},
|
||||||
// mui-overrides
|
// mui-overrides
|
||||||
cardContentRoot: {
|
cardContentRoot: {
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Downshift
|
|
||||||
id={name}
|
|
||||||
itemToString={it => {
|
|
||||||
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
|
|
||||||
}) => (
|
|
||||||
<div className={classes.container}>
|
|
||||||
{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
|
|
||||||
})}
|
|
||||||
<Popper
|
|
||||||
open={isOpen}
|
|
||||||
anchorEl={popperNode}
|
|
||||||
modifiers={{ flip: { enabled: true } }}
|
|
||||||
style={{ zIndex: 9999 }}>
|
|
||||||
<div
|
|
||||||
{...(isOpen
|
|
||||||
? getMenuProps({}, { suppressRefError: true })
|
|
||||||
: {})}>
|
|
||||||
<Paper
|
|
||||||
square
|
|
||||||
style={{
|
|
||||||
minWidth: popperNode ? popperNode.clientWidth + 2 : null
|
|
||||||
}}>
|
|
||||||
{filterSuggestions(
|
|
||||||
suggestions,
|
|
||||||
inputValue2,
|
|
||||||
value ? R.of(value) : [],
|
|
||||||
code,
|
|
||||||
display
|
|
||||||
).map((suggestion, index) =>
|
|
||||||
renderSuggestion({
|
|
||||||
suggestion,
|
|
||||||
index,
|
|
||||||
itemProps: getItemProps({ item: suggestion }),
|
|
||||||
highlightedIndex,
|
|
||||||
selectedItem: selectedItem2,
|
|
||||||
code,
|
|
||||||
display
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</div>
|
|
||||||
</Popper>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Downshift>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export default withStyles(styles)(Autocomplete)
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Downshift
|
|
||||||
id={name}
|
|
||||||
itemToString={it => {
|
|
||||||
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
|
|
||||||
}) => (
|
|
||||||
<div className={classnames(classes.container, className)}>
|
|
||||||
{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
|
|
||||||
})}
|
|
||||||
<Popper
|
|
||||||
open={isOpen}
|
|
||||||
anchorEl={popperNode}
|
|
||||||
modifiers={{ flip: { enabled: true } }}
|
|
||||||
style={{ zIndex: 9999 }}>
|
|
||||||
<div
|
|
||||||
{...(isOpen
|
|
||||||
? getMenuProps({}, { suppressRefError: true })
|
|
||||||
: {})}>
|
|
||||||
<Paper
|
|
||||||
square
|
|
||||||
style={{
|
|
||||||
minWidth: popperNode ? popperNode.clientWidth + 2 : null
|
|
||||||
}}>
|
|
||||||
{filterSuggestions(
|
|
||||||
suggestions,
|
|
||||||
inputValue2,
|
|
||||||
value ? R.of(value) : [],
|
|
||||||
code,
|
|
||||||
display
|
|
||||||
).map((suggestion, index) =>
|
|
||||||
renderSuggestion({
|
|
||||||
suggestion,
|
|
||||||
index,
|
|
||||||
itemProps: getItemProps({ item: suggestion }),
|
|
||||||
highlightedIndex,
|
|
||||||
selectedItem: selectedItem2,
|
|
||||||
code,
|
|
||||||
display
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
</Paper>
|
|
||||||
</div>
|
|
||||||
</Popper>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Downshift>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export default withStyles(styles)(AutocompleteSelect)
|
|
||||||
|
|
@ -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 (
|
|
||||||
<TextInput
|
|
||||||
name={name}
|
|
||||||
onChange={onChange}
|
|
||||||
onBlur={onBlur}
|
|
||||||
value={value}
|
|
||||||
error={!!error}
|
|
||||||
InputProps={InputProps}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<StyledMenuItem
|
|
||||||
{...itemProps}
|
|
||||||
key={suggestion[code]}
|
|
||||||
selected={isHighlighted}
|
|
||||||
component="div">
|
|
||||||
{suggestion[display]}
|
|
||||||
</StyledMenuItem>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 }
|
|
||||||
|
|
@ -10,34 +10,45 @@ const Autocomplete = ({
|
||||||
limit = 5,
|
limit = 5,
|
||||||
options,
|
options,
|
||||||
label,
|
label,
|
||||||
shouldAdd,
|
valueProp,
|
||||||
getOptionSelected,
|
|
||||||
forceShowValue,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
multiple,
|
multiple,
|
||||||
|
onChange,
|
||||||
getLabel,
|
getLabel,
|
||||||
|
value: outsideValue,
|
||||||
error,
|
error,
|
||||||
fullWidth,
|
fullWidth,
|
||||||
textAlign,
|
textAlign,
|
||||||
size,
|
size,
|
||||||
...props
|
...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 getValue = () => {
|
||||||
const find = R.find(it => compare(value, it))
|
if (!valueProp) return outsideValue
|
||||||
|
|
||||||
if (forceShowValue && !multiple && value && !find(options)) {
|
const transform = multiple
|
||||||
iOptions = R.concat(options, [value])
|
? 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 (
|
||||||
<MAutocomplete
|
<MAutocomplete
|
||||||
options={iOptions}
|
options={options}
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={iOnChange}
|
||||||
getOptionLabel={getLabel}
|
getOptionLabel={getLabel}
|
||||||
forcePopupIcon={false}
|
forcePopupIcon={false}
|
||||||
filterOptions={createFilterOptions({ ignoreAccents: true, limit })}
|
filterOptions={createFilterOptions({ ignoreAccents: true, limit })}
|
||||||
|
|
@ -47,14 +58,14 @@ const Autocomplete = ({
|
||||||
ChipProps={{ onDelete: null }}
|
ChipProps={{ onDelete: null }}
|
||||||
blurOnSelect
|
blurOnSelect
|
||||||
clearOnEscape
|
clearOnEscape
|
||||||
getOptionSelected={getOptionSelected}
|
getOptionSelected={R.eqProps(valueProp)}
|
||||||
{...props}
|
{...props}
|
||||||
renderInput={params => {
|
renderInput={params => {
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
{...params}
|
{...params}
|
||||||
label={label}
|
label={label}
|
||||||
value={value}
|
value={outsideValue}
|
||||||
error={error}
|
error={error}
|
||||||
size={size}
|
size={size}
|
||||||
fullWidth={fullWidth}
|
fullWidth={fullWidth}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
function Radio({ label, ...props }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<label>
|
|
||||||
<input type="radio" className="with-gap" name="gruop1" />
|
|
||||||
<span>{label || ''}</span>
|
|
||||||
</label>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Radio
|
|
||||||
|
|
@ -1,64 +1,55 @@
|
||||||
import {
|
import {
|
||||||
Radio as MaterialRadio,
|
Radio,
|
||||||
RadioGroup as MaterialRadioGroup,
|
RadioGroup as MRadioGroup,
|
||||||
FormControlLabel
|
FormControlLabel,
|
||||||
|
makeStyles
|
||||||
} from '@material-ui/core'
|
} from '@material-ui/core'
|
||||||
import { withStyles } from '@material-ui/styles'
|
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { secondaryColor } from '../../../styling/variables'
|
import { Label1 } from 'src/components/typography'
|
||||||
import typographyStyles from '../../typography/styles'
|
|
||||||
|
|
||||||
const { p } = typographyStyles
|
const styles = {
|
||||||
|
|
||||||
const GreenRadio = withStyles({
|
|
||||||
root: {
|
|
||||||
color: secondaryColor,
|
|
||||||
padding: [[9, 8, 9, 9]],
|
|
||||||
'&$checked': {
|
|
||||||
color: secondaryColor
|
|
||||||
}
|
|
||||||
},
|
|
||||||
checked: {}
|
|
||||||
})(props => <MaterialRadio color="default" {...props} />)
|
|
||||||
|
|
||||||
const Label = withStyles({
|
|
||||||
label: {
|
label: {
|
||||||
extend: p
|
height: 16,
|
||||||
|
lineHeight: '16px',
|
||||||
|
margin: [[0, 0, 4, 0]],
|
||||||
|
paddingLeft: 3
|
||||||
}
|
}
|
||||||
})(props => <FormControlLabel {...props} />)
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
/* options = [{ label, value }]
|
|
||||||
*/
|
|
||||||
const RadioGroup = ({
|
const RadioGroup = ({
|
||||||
name,
|
name,
|
||||||
|
label,
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
ariaLabel,
|
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
...props
|
labelClassName,
|
||||||
|
radioClassName
|
||||||
}) => {
|
}) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{options && (
|
{label && <Label1 className={classes.label}>{label}</Label1>}
|
||||||
<MaterialRadioGroup
|
<MRadioGroup
|
||||||
aria-label={ariaLabel}
|
name={name}
|
||||||
name={name}
|
value={value}
|
||||||
value={value}
|
onChange={onChange}
|
||||||
onChange={onChange}
|
className={classnames(className)}>
|
||||||
className={classnames(className)}>
|
{options.map((option, idx) => (
|
||||||
{options.map((option, idx) => (
|
<FormControlLabel
|
||||||
<Label
|
key={idx}
|
||||||
key={idx}
|
value={option.code}
|
||||||
value={option.value}
|
control={<Radio className={radioClassName} />}
|
||||||
control={<GreenRadio />}
|
label={option.display}
|
||||||
label={option.label}
|
className={classnames(labelClassName)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</MaterialRadioGroup>
|
</MRadioGroup>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
new-lamassu-admin/src/components/inputs/base/SecretInput.js
Normal file
36
new-lamassu-admin/src/components/inputs/base/SecretInput.js
Normal file
|
|
@ -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 (
|
||||||
|
<TextInput
|
||||||
|
{...props}
|
||||||
|
type="password"
|
||||||
|
onFocus={iOnFocus}
|
||||||
|
onBlur={iOnBlur}
|
||||||
|
value={value}
|
||||||
|
InputProps={{ value: !focused ? tempValue : value }}
|
||||||
|
InputLabelProps={{ shrink: previouslyFilled || focused }}
|
||||||
|
placeholder={previouslyFilled ? placeholder : ''}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default SecretInput
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import Autocomplete from './Autocomplete'
|
import Autocomplete from './Autocomplete'
|
||||||
import Checkbox from './Checkbox'
|
import Checkbox from './Checkbox'
|
||||||
import RadioGroup from './RadioGroup'
|
import RadioGroup from './RadioGroup'
|
||||||
|
import SecretInput from './SecretInput'
|
||||||
import Switch from './Switch'
|
import Switch from './Switch'
|
||||||
import TextInput from './TextInput'
|
import TextInput from './TextInput'
|
||||||
|
|
||||||
export { Checkbox, TextInput, Switch, RadioGroup, Autocomplete }
|
export { Checkbox, TextInput, Switch, SecretInput, RadioGroup, Autocomplete }
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
|
import { useFormikContext } from 'formik'
|
||||||
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Autocomplete } from '../base'
|
import { Autocomplete } from '../base'
|
||||||
|
|
||||||
const AutocompleteFormik = props => {
|
const AutocompleteFormik = ({ options, ...props }) => {
|
||||||
const { name, onBlur, value } = props.field
|
const { name, onBlur, value } = props.field
|
||||||
const { touched, errors, setFieldValue } = props.form
|
const { touched, errors, setFieldValue } = props.form
|
||||||
const error = !!(touched[name] && errors[name])
|
const error = !!(touched[name] && errors[name])
|
||||||
|
const { initialValues } = useFormikContext()
|
||||||
|
|
||||||
|
const iOptions =
|
||||||
|
R.type(options) === 'Function' ? options(initialValues) : options
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
|
|
@ -14,6 +20,7 @@ const AutocompleteFormik = props => {
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
value={value}
|
value={value}
|
||||||
error={error}
|
error={error}
|
||||||
|
options={iOptions}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,24 @@
|
||||||
import React, { memo } from 'react'
|
import React, { memo } from 'react'
|
||||||
import { makeStyles } from '@material-ui/core'
|
|
||||||
|
|
||||||
import { Label1 } from 'src/components/typography'
|
|
||||||
|
|
||||||
import { RadioGroup } from '../base'
|
import { RadioGroup } from '../base'
|
||||||
|
|
||||||
const styles = {
|
const RadioGroupFormik = memo(({ label, ...props }) => {
|
||||||
label: {
|
|
||||||
height: 16,
|
|
||||||
lineHeight: '16px',
|
|
||||||
margin: [[0, 0, 4, 0]],
|
|
||||||
paddingLeft: 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
|
||||||
|
|
||||||
const RadioGroupFormik = memo(({ ...props }) => {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const { name, onChange, value } = props.field
|
const { name, onChange, value } = props.field
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<RadioGroup
|
||||||
{props.label && <Label1 className={classes.label}>{props.label}</Label1>}
|
name={name}
|
||||||
<RadioGroup
|
label={label}
|
||||||
name={name}
|
value={value}
|
||||||
value={value}
|
options={props.options}
|
||||||
options={props.options}
|
ariaLabel={name}
|
||||||
ariaLabel={name}
|
onChange={e => {
|
||||||
onChange={e => {
|
onChange(e)
|
||||||
onChange(e)
|
props.resetError()
|
||||||
props.resetError()
|
}}
|
||||||
}}
|
className={props.className}
|
||||||
className={props.className}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,22 @@
|
||||||
import { makeStyles } from '@material-ui/core'
|
import React, { memo } from 'react'
|
||||||
import classnames from 'classnames'
|
|
||||||
import React, { memo, useState } from 'react'
|
|
||||||
|
|
||||||
import TextInputFormik from './TextInput'
|
import { SecretInput } from '../base'
|
||||||
import { styles } from './TextInput.styles'
|
|
||||||
|
|
||||||
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 error = !!(touched[name] && errors[name])
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<SecretInput
|
||||||
<span className={classnames(spanClass)} aria-hidden="true">
|
name={name}
|
||||||
⚬ ⚬ ⚬ This field is set ⚬ ⚬ ⚬
|
onChange={onChange}
|
||||||
</span>
|
onBlur={onBlur}
|
||||||
<TextInputFormik
|
value={value}
|
||||||
{...props}
|
error={error}
|
||||||
onFocus={handleFocus}
|
{...props}
|
||||||
className={classnames(inputClass, className)}
|
/>
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,8 @@
|
||||||
import AutocompleteSelect from './autocomplete/AutocompleteSelect'
|
import Autocomplete from './base/Autocomplete'
|
||||||
import Checkbox from './base/Checkbox'
|
import Checkbox from './base/Checkbox'
|
||||||
import Radio from './base/Radio'
|
|
||||||
import RadioGroup from './base/RadioGroup'
|
import RadioGroup from './base/RadioGroup'
|
||||||
import Select from './base/Select'
|
import Select from './base/Select'
|
||||||
import Switch from './base/Switch'
|
import Switch from './base/Switch'
|
||||||
import TextInput from './base/TextInput'
|
import TextInput from './base/TextInput'
|
||||||
|
|
||||||
export {
|
export { Autocomplete, TextInput, Checkbox, Switch, Select, RadioGroup }
|
||||||
AutocompleteSelect,
|
|
||||||
TextInput,
|
|
||||||
Radio,
|
|
||||||
Checkbox,
|
|
||||||
Switch,
|
|
||||||
Select,
|
|
||||||
RadioGroup
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ import classnames from 'classnames'
|
||||||
import React, { memo, useState } from 'react'
|
import React, { memo, useState } from 'react'
|
||||||
import { NavLink } from 'react-router-dom'
|
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 { ReactComponent as Logo } from 'src/styling/icons/menu/logo.svg'
|
||||||
import AddMachine from 'src/pages/AddMachine'
|
import AddMachine from 'src/pages/AddMachine'
|
||||||
|
|
||||||
import styles from './Header.styles'
|
import styles from './Header.styles'
|
||||||
import { Link } from './buttons'
|
|
||||||
import { H4 } from './typography'
|
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
import {
|
import {
|
||||||
version,
|
version,
|
||||||
mainWidth,
|
mainWidth,
|
||||||
|
|
@ -9,8 +10,6 @@ import {
|
||||||
fontColor
|
fontColor
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
import typographyStyles from './typography/styles'
|
|
||||||
|
|
||||||
const { tl2, p } = typographyStyles
|
const { tl2, p } = typographyStyles
|
||||||
|
|
||||||
let headerHeight = spacer * 7
|
let headerHeight = spacer * 7
|
||||||
|
|
@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import ErrorMessage from 'src/components/ErrorMessage'
|
import ErrorMessage from 'src/components/ErrorMessage'
|
||||||
import { TL1 } from 'src/components/typography'
|
import Subtitle from 'src/components/Subtitle'
|
||||||
|
|
||||||
import styles from './Section.styles'
|
import styles from './Section.styles'
|
||||||
|
|
||||||
|
|
@ -12,10 +12,12 @@ const Section = ({ error, children, title }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
return (
|
return (
|
||||||
<div className={classes.section}>
|
<div className={classes.section}>
|
||||||
<div className={classes.sectionHeader}>
|
{(title || error) && (
|
||||||
<TL1 className={classes.sectionTitle}>{title}</TL1>
|
<div className={classes.sectionHeader}>
|
||||||
{error && <ErrorMessage>Failed to save changes</ErrorMessage>}
|
<Subtitle className={classes.sectionTitle}>{title}</Subtitle>
|
||||||
</div>
|
{error && <ErrorMessage>Failed to save changes</ErrorMessage>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { offColor } from 'src/styling/variables'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
section: {
|
section: {
|
||||||
marginBottom: 72
|
marginBottom: 72
|
||||||
|
|
@ -9,7 +7,6 @@ export default {
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
color: offColor,
|
|
||||||
margin: [[16, 20, 23, 0]]
|
margin: [[16, 20, 23, 0]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
import { respondTo } from 'src/styling/helpers'
|
import { respondTo } from 'src/styling/helpers'
|
||||||
import {
|
import {
|
||||||
primaryColor,
|
primaryColor,
|
||||||
|
|
@ -7,8 +8,6 @@ import {
|
||||||
xxl
|
xxl
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
import typographyStyles from './typography/styles'
|
|
||||||
|
|
||||||
const { tl2, p } = typographyStyles
|
const { tl2, p } = typographyStyles
|
||||||
|
|
||||||
const sidebarColor = zircon
|
const sidebarColor = zircon
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import ErrorMessage from 'src/components/ErrorMessage'
|
||||||
import Title from 'src/components/Title'
|
import Title from 'src/components/Title'
|
||||||
|
|
||||||
import styles from './TitleSection.styles'
|
import styles from './TitleSection.styles'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const TitleSection = ({ title }) => {
|
const TitleSection = ({ title, error }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
return (
|
return (
|
||||||
<div className={classes.titleWrapper}>
|
<div className={classes.titleWrapper}>
|
||||||
<div className={classes.titleAndButtonsContainer}>
|
<div className={classes.titleAndButtonsContainer}>
|
||||||
<Title>{title}</Title>
|
<Title>{title}</Title>
|
||||||
|
{error && (
|
||||||
|
<ErrorMessage className={classes.error}>Failed to save</ErrorMessage>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,7 @@ export default {
|
||||||
titleAndButtonsContainer: {
|
titleAndButtonsContainer: {
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
},
|
},
|
||||||
iconButton: {
|
error: {
|
||||||
border: 'none',
|
marginLeft: 12
|
||||||
outline: 0,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,130 +2,46 @@ import { makeStyles } from '@material-ui/core'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Table, THead, TBody, Td, Th } from 'src/components/fake-table/Table'
|
import { IconButton } from 'src/components/buttons'
|
||||||
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 {
|
import {
|
||||||
offColor,
|
Table,
|
||||||
tableDisabledHeaderColor,
|
THead,
|
||||||
tableNewDisabledHeaderColor,
|
TBody,
|
||||||
secondaryColorDarker
|
Td,
|
||||||
} from 'src/styling/variables'
|
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 = ({
|
const SingleRowTable = ({
|
||||||
width = 380,
|
width = 378,
|
||||||
height = 160,
|
height = 128,
|
||||||
title,
|
title,
|
||||||
items,
|
items,
|
||||||
onEdit,
|
onEdit,
|
||||||
disabled,
|
className
|
||||||
newService,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}) => {
|
}) => {
|
||||||
const editButtonSize = 54
|
const classes = useStyles({ width, height })
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{items && (
|
<Table className={classnames(className, classes.table)}>
|
||||||
<Table className={classnames(className, classes.wrapper)}>
|
<THead>
|
||||||
<THead className={classnames(headerClasses)}>
|
<Th className={classes.head}>
|
||||||
<Th width={width - editButtonSize}>
|
{title}
|
||||||
{title}
|
<IconButton onClick={onEdit} className={classes.button}>
|
||||||
{newService && <span className={classes.spanNew}>New</span>}
|
<EditIcon />
|
||||||
</Th>
|
</IconButton>
|
||||||
<Th width={editButtonSize} className={classes.buttonTh}>
|
</Th>
|
||||||
{!disabled && (
|
</THead>
|
||||||
<button className={classes.editButton} onClick={onEdit}>
|
<TBody>
|
||||||
<EditIcon />
|
<Tr className={classes.tr}>
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{disabled && (
|
|
||||||
<button className={classes.editButton}>
|
|
||||||
<DeleteIcon />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Th>
|
|
||||||
</THead>
|
|
||||||
<TBody className={classnames(bodyClasses)}>
|
|
||||||
<Td width={width}>
|
<Td width={width}>
|
||||||
{!disabled && (
|
{items && (
|
||||||
<>
|
<>
|
||||||
{items[0] && (
|
{items[0] && (
|
||||||
<div className={classes.itemWrapper}>
|
<div className={classes.itemWrapper}>
|
||||||
|
|
@ -141,18 +57,10 @@ const SingleRowTable = ({
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{disabled && (
|
|
||||||
<div className={classes.disabledWrapper}>
|
|
||||||
<span>
|
|
||||||
<WarningIcon />
|
|
||||||
</span>
|
|
||||||
<span>This service is not being used</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Td>
|
</Td>
|
||||||
</TBody>
|
</Tr>
|
||||||
</Table>
|
</TBody>
|
||||||
)}
|
</Table>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
AutoSizer,
|
AutoSizer,
|
||||||
|
|
@ -8,33 +9,31 @@ import {
|
||||||
CellMeasurerCache
|
CellMeasurerCache
|
||||||
} from 'react-virtualized'
|
} 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 ExpandClosedIcon } from 'src/styling/icons/action/expand/closed.svg'
|
||||||
import { ReactComponent as ExpandOpenIcon } from 'src/styling/icons/action/expand/open.svg'
|
import { ReactComponent as ExpandOpenIcon } from 'src/styling/icons/action/expand/open.svg'
|
||||||
import { mainWidth } from 'src/styling/variables'
|
|
||||||
|
|
||||||
const styles = {
|
import styles from './DataTable.styles'
|
||||||
expandButton: {
|
|
||||||
border: 'none',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
cursor: 'pointer',
|
|
||||||
padding: 4
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
borderRadius: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const ExpRow = ({
|
const Row = ({
|
||||||
id,
|
id,
|
||||||
elements,
|
elements,
|
||||||
data,
|
data,
|
||||||
|
width,
|
||||||
Details,
|
Details,
|
||||||
expanded,
|
expanded,
|
||||||
expandRow,
|
expandRow,
|
||||||
...props
|
expWidth,
|
||||||
|
expandable
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
|
@ -44,34 +43,25 @@ const ExpRow = ({
|
||||||
className={classnames(classes.row)}
|
className={classnames(classes.row)}
|
||||||
error={data.error}
|
error={data.error}
|
||||||
errorMessage={data.errorMessage}>
|
errorMessage={data.errorMessage}>
|
||||||
{elements
|
{elements.map(({ view = it => it?.toString(), ...props }, idx) => (
|
||||||
.slice(0, -1)
|
<Td key={idx} {...props}>
|
||||||
.map(
|
{view(data)}
|
||||||
(
|
</Td>
|
||||||
{ width, className, textAlign, view = it => it?.toString() },
|
))}
|
||||||
idx
|
{expandable && (
|
||||||
) => (
|
<Td width={expWidth} textAlign="center">
|
||||||
<Td
|
<button
|
||||||
key={idx}
|
onClick={() => expandRow(id)}
|
||||||
width={width}
|
className={classes.expandButton}>
|
||||||
className={className}
|
{expanded && <ExpandOpenIcon />}
|
||||||
textAlign={textAlign}>
|
{!expanded && <ExpandClosedIcon />}
|
||||||
{view(data)}
|
</button>
|
||||||
</Td>
|
</Td>
|
||||||
)
|
)}
|
||||||
)}
|
|
||||||
<Td width={elements[elements.length - 1].width}>
|
|
||||||
<button
|
|
||||||
onClick={() => expandRow(id)}
|
|
||||||
className={classes.expandButton}>
|
|
||||||
{expanded && <ExpandOpenIcon />}
|
|
||||||
{!expanded && <ExpandClosedIcon />}
|
|
||||||
</button>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
</Tr>
|
||||||
{expanded && (
|
{expandable && expanded && (
|
||||||
<Tr className={classes.detailsRow}>
|
<Tr className={classes.detailsRow}>
|
||||||
<Td width={mainWidth}>
|
<Td width={width}>
|
||||||
<Details it={data} />
|
<Details it={data} />
|
||||||
</Td>
|
</Td>
|
||||||
</Tr>
|
</Tr>
|
||||||
|
|
@ -80,18 +70,22 @@ const ExpRow = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rows = [{ columns = [{ name, value, className, textAlign, width }], details, className, error, errorMessage }]
|
const DataTable = ({
|
||||||
* Don't forget to include the width of the last (expand button) column!
|
|
||||||
*/
|
|
||||||
const ExpTable = ({
|
|
||||||
elements = [],
|
elements = [],
|
||||||
data = [],
|
data = [],
|
||||||
Details,
|
Details,
|
||||||
className,
|
className,
|
||||||
|
expandable,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const [expanded, setExpanded] = useState(null)
|
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 => {
|
const expandRow = id => {
|
||||||
setExpanded(id === expanded ? null : id)
|
setExpanded(id === expanded ? null : id)
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +95,7 @@ const ExpTable = ({
|
||||||
fixedWidth: true
|
fixedWidth: true
|
||||||
})
|
})
|
||||||
|
|
||||||
function rowRenderer({ index, isScrolling, key, parent, style }) {
|
function rowRenderer({ index, key, parent, style }) {
|
||||||
return (
|
return (
|
||||||
<CellMeasurer
|
<CellMeasurer
|
||||||
cache={cache}
|
cache={cache}
|
||||||
|
|
@ -110,13 +104,16 @@ const ExpTable = ({
|
||||||
parent={parent}
|
parent={parent}
|
||||||
rowIndex={index}>
|
rowIndex={index}>
|
||||||
<div style={style}>
|
<div style={style}>
|
||||||
<ExpRow
|
<Row
|
||||||
|
width={width}
|
||||||
id={index}
|
id={index}
|
||||||
|
expWidth={expWidth}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={data[index]}
|
data={data[index]}
|
||||||
Details={Details}
|
Details={Details}
|
||||||
expanded={index === expanded}
|
expanded={index === expanded}
|
||||||
expandRow={expandRow}
|
expandRow={expandRow}
|
||||||
|
expandable={expandable}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CellMeasurer>
|
</CellMeasurer>
|
||||||
|
|
@ -124,27 +121,28 @@ const ExpTable = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Table className={classes.table}>
|
||||||
<div>
|
<THead>
|
||||||
<THead>
|
{elements.map(({ width, className, textAlign, header }, idx) => (
|
||||||
{elements.map(({ width, className, textAlign, header }, idx) => (
|
<Th
|
||||||
<Th
|
key={idx}
|
||||||
key={idx}
|
width={width}
|
||||||
width={width}
|
className={className}
|
||||||
className={className}
|
textAlign={textAlign}>
|
||||||
textAlign={textAlign}>
|
{header}
|
||||||
{header}
|
</Th>
|
||||||
</Th>
|
))}
|
||||||
))}
|
{expandable && <Th width={expWidth}></Th>}
|
||||||
</THead>
|
</THead>
|
||||||
</div>
|
<TBody className={classes.body}>
|
||||||
<div style={{ flex: '1 1 auto' }}>
|
|
||||||
<AutoSizer disableWidth>
|
<AutoSizer disableWidth>
|
||||||
{({ height }) => (
|
{({ height }) => (
|
||||||
<List
|
<List
|
||||||
|
// this has to be in a style because of how the component works
|
||||||
|
style={{ overflow: 'inherit', outline: 'none' }}
|
||||||
{...props}
|
{...props}
|
||||||
height={height}
|
height={height}
|
||||||
width={mainWidth}
|
width={width}
|
||||||
rowCount={data.length}
|
rowCount={data.length}
|
||||||
rowHeight={cache.rowHeight}
|
rowHeight={cache.rowHeight}
|
||||||
rowRenderer={rowRenderer}
|
rowRenderer={rowRenderer}
|
||||||
|
|
@ -153,9 +151,9 @@ const ExpTable = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
</div>
|
</TBody>
|
||||||
</>
|
</Table>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ExpTable
|
export default DataTable
|
||||||
20
new-lamassu-admin/src/components/tables/DataTable.styles.js
Normal file
20
new-lamassu-admin/src/components/tables/DataTable.styles.js
Normal file
|
|
@ -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'
|
||||||
|
})
|
||||||
|
}
|
||||||
12
new-lamassu-admin/src/components/tables/Stripes.js
Normal file
12
new-lamassu-admin/src/components/tables/Stripes.js
Normal file
|
|
@ -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 }) => (
|
||||||
|
<Td width={width}>
|
||||||
|
<StripesSvg />
|
||||||
|
</Td>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default Stripes
|
||||||
|
|
@ -7,7 +7,7 @@ import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import Title from 'src/components/Title'
|
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 TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import moment from 'moment'
|
||||||
import QRCode from 'qrcode.react'
|
import QRCode from 'qrcode.react'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
import Sidebar from 'src/components/Sidebar'
|
|
||||||
import TableLabel from 'src/components/TableLabel'
|
import TableLabel from 'src/components/TableLabel'
|
||||||
import Title from 'src/components/Title'
|
import Title from 'src/components/Title'
|
||||||
import { Tr, Td, THead, TBody, Table } from 'src/components/fake-table/Table'
|
import { Tr, Td, THead, TBody, Table } from 'src/components/fake-table/Table'
|
||||||
|
import Sidebar from 'src/components/layout/Sidebar'
|
||||||
import {
|
import {
|
||||||
H3,
|
H3,
|
||||||
Info1,
|
Info1,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import React from 'react'
|
||||||
import { Table as EditableTable } from 'src/components/editableTable'
|
import { Table as EditableTable } from 'src/components/editableTable'
|
||||||
import Section from 'src/components/layout/Section'
|
import Section from 'src/components/layout/Section'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { fromServer, toServer } from 'src/utils/config'
|
import { fromNamespace, toNamespace } from 'src/utils/config'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
mainFields,
|
mainFields,
|
||||||
|
|
@ -55,25 +55,27 @@ const Locales = ({ name: SCREEN_KEY }) => {
|
||||||
refetchQueries: () => ['getData']
|
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 locale = config && !R.isEmpty(config) ? config : localeDefaults
|
||||||
|
|
||||||
const save = it => {
|
const save = it => {
|
||||||
const config = toServer(SCREEN_KEY)(it.locale[0])
|
const config = toNamespace(SCREEN_KEY)(it.locale[0])
|
||||||
return saveConfig({ variables: { config } })
|
return saveConfig({ variables: { config } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveOverrides = it => {
|
const saveOverrides = it => {
|
||||||
const config = toServer(SCREEN_KEY)(it)
|
const config = toNamespace(SCREEN_KEY)(it)
|
||||||
return saveConfig({ variables: { config } })
|
return saveConfig({ variables: { config } })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TitleSection title="Locales" />
|
<TitleSection title="Locales" />
|
||||||
<Section title="Default settings">
|
<Section>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
|
title="Default settings"
|
||||||
|
titleLg
|
||||||
name="locale"
|
name="locale"
|
||||||
enableEdit
|
enableEdit
|
||||||
initialValues={locale}
|
initialValues={locale}
|
||||||
|
|
@ -83,8 +85,10 @@ const Locales = ({ name: SCREEN_KEY }) => {
|
||||||
elements={mainFields(data)}
|
elements={mainFields(data)}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
<Section title="Overrides">
|
<Section>
|
||||||
<EditableTable
|
<EditableTable
|
||||||
|
title="Overrides"
|
||||||
|
titleLg
|
||||||
name="overrides"
|
name="overrides"
|
||||||
enableDelete
|
enableDelete
|
||||||
enableEdit
|
enableEdit
|
||||||
|
|
|
||||||
|
|
@ -3,77 +3,98 @@ import * as Yup from 'yup'
|
||||||
|
|
||||||
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
|
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
|
||||||
|
|
||||||
const displayCodeArray = it => {
|
|
||||||
return it ? R.compose(R.join(', '), R.map(R.path(['code'])))(it) : it
|
|
||||||
}
|
|
||||||
|
|
||||||
const getFields = (getData, names) => {
|
const getFields = (getData, names) => {
|
||||||
return R.filter(it => R.includes(it.name, names), allFields(getData))
|
return R.filter(it => R.includes(it.name, names), allFields(getData))
|
||||||
}
|
}
|
||||||
|
|
||||||
const allFields = getData => [
|
const allFields = getData => {
|
||||||
{
|
const getView = (data, code, compare) => it => {
|
||||||
name: 'machine',
|
if (!data) return ''
|
||||||
width: 200,
|
|
||||||
size: 'sm',
|
return R.compose(
|
||||||
view: R.path(['name']),
|
R.prop(code),
|
||||||
input: Autocomplete,
|
R.find(R.propEq(compare ?? 'code', it))
|
||||||
inputProps: {
|
)(data)
|
||||||
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 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 mainFields = auxData => {
|
||||||
const getData = R.path(R.__, auxData)
|
const getData = R.path(R.__, auxData)
|
||||||
|
|
@ -98,29 +119,29 @@ const overrides = auxData => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocaleSchema = Yup.object().shape({
|
const LocaleSchema = Yup.object().shape({
|
||||||
country: Yup.object().required('Required'),
|
country: Yup.string().required('Required'),
|
||||||
fiatCurrency: Yup.object().required('Required'),
|
fiatCurrency: Yup.string().required('Required'),
|
||||||
languages: Yup.array().required('Required'),
|
languages: Yup.array().required('Required'),
|
||||||
cryptoCurrencies: Yup.array().required('Required')
|
cryptoCurrencies: Yup.array().required('Required')
|
||||||
})
|
})
|
||||||
|
|
||||||
const OverridesSchema = Yup.object().shape({
|
const OverridesSchema = Yup.object().shape({
|
||||||
machine: Yup.object().required('Required'),
|
machine: Yup.string().required('Required'),
|
||||||
country: Yup.object().required('Required'),
|
country: Yup.string().required('Required'),
|
||||||
languages: Yup.array().required('Required'),
|
languages: Yup.array().required('Required'),
|
||||||
cryptoCurrencies: Yup.array().required('Required')
|
cryptoCurrencies: Yup.array().required('Required')
|
||||||
})
|
})
|
||||||
|
|
||||||
const localeDefaults = {
|
const localeDefaults = {
|
||||||
country: null,
|
country: '',
|
||||||
fiatCurrency: null,
|
fiatCurrency: '',
|
||||||
languages: [],
|
languages: [],
|
||||||
cryptoCurrencies: []
|
cryptoCurrencies: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const overridesDefaults = {
|
const overridesDefaults = {
|
||||||
machine: null,
|
machine: '',
|
||||||
country: null,
|
country: '',
|
||||||
languages: [],
|
languages: [],
|
||||||
cryptoCurrencies: []
|
cryptoCurrencies: []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
|
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
|
||||||
import Sidebar from 'src/components/Sidebar'
|
|
||||||
import Title from 'src/components/Title'
|
import Title from 'src/components/Title'
|
||||||
import { FeatureButton, SimpleButton } from 'src/components/buttons'
|
import { FeatureButton, SimpleButton } from 'src/components/buttons'
|
||||||
|
import Sidebar from 'src/components/layout/Sidebar'
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableHead,
|
TableHead,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
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'
|
import Section from '../../components/layout/Section'
|
||||||
|
|
||||||
|
|
@ -49,19 +49,19 @@ const Notifications = ({ name: SCREEN_KEY }) => {
|
||||||
onError: error => setError({ error })
|
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 machines = data?.machines
|
||||||
const cryptoCurrencies = data?.cryptoCurrencies
|
const cryptoCurrencies = data?.cryptoCurrencies
|
||||||
|
|
||||||
// TODO check path when locales is finished
|
// TODO improve the way of fetching this
|
||||||
const currency = R.path(['locales_currency'])(data?.config ?? {})
|
const currency = R.path(['locale_fiatCurrency', 'code'])(data?.config ?? {})
|
||||||
|
|
||||||
const save = (section, rawConfig) => {
|
const save = R.curry((section, rawConfig) => {
|
||||||
const config = toServer(SCREEN_KEY)(rawConfig)
|
const config = toNamespace(SCREEN_KEY)(rawConfig)
|
||||||
setSection(section)
|
setSection(section)
|
||||||
setError(null)
|
setError(null)
|
||||||
return saveConfig({ variables: { config } })
|
return saveConfig({ variables: { config } })
|
||||||
}
|
})
|
||||||
|
|
||||||
const setEditing = (key, state) => {
|
const setEditing = (key, state) => {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Link } from 'src/components/buttons'
|
import { Link, IconButton } from 'src/components/buttons'
|
||||||
import { H4 } from 'src/components/typography'
|
import { H4 } from 'src/components/typography'
|
||||||
import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.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 EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
||||||
|
|
@ -17,12 +17,12 @@ const Header = ({ title, editing, disabled, setEditing }) => {
|
||||||
<div className={classes.header}>
|
<div className={classes.header}>
|
||||||
<H4 className={classes.title}>{title}</H4>
|
<H4 className={classes.title}>{title}</H4>
|
||||||
{!editing && (
|
{!editing && (
|
||||||
<button
|
<IconButton
|
||||||
onClick={() => setEditing(true)}
|
onClick={() => setEditing(true)}
|
||||||
className={classes.button}
|
className={classes.button}
|
||||||
disabled={disabled}>
|
disabled={disabled}>
|
||||||
{disabled ? <DisabledEditIcon /> : <EditIcon />}
|
{disabled ? <DisabledEditIcon /> : <EditIcon />}
|
||||||
</button>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
{editing && (
|
{editing && (
|
||||||
<div className={classes.editingButtons}>
|
<div className={classes.editingButtons}>
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ const NAME = 'cryptoBalanceOverrides'
|
||||||
|
|
||||||
const CryptoBalanceOverrides = ({ section }) => {
|
const CryptoBalanceOverrides = ({ section }) => {
|
||||||
const {
|
const {
|
||||||
cryptoCurrencies,
|
cryptoCurrencies = [],
|
||||||
data,
|
data,
|
||||||
save,
|
save,
|
||||||
currency,
|
currency,
|
||||||
|
|
@ -32,12 +32,17 @@ const CryptoBalanceOverrides = ({ section }) => {
|
||||||
return save(newOverrides)
|
return save(newOverrides)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSuggestions = () => {
|
const overridenCryptos = R.map(R.prop(CRYPTOCURRENCY_KEY))(setupValues)
|
||||||
const overridenCryptos = R.map(
|
const suggestionFilter = R.filter(
|
||||||
override => override[CRYPTOCURRENCY_KEY],
|
it => !R.contains(it.code, overridenCryptos)
|
||||||
setupValues
|
)
|
||||||
|
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 = {
|
const initialValues = {
|
||||||
|
|
@ -60,7 +65,11 @@ const CryptoBalanceOverrides = ({ section }) => {
|
||||||
.required()
|
.required()
|
||||||
})
|
})
|
||||||
|
|
||||||
const suggestions = getSuggestions()
|
const viewCrypto = it =>
|
||||||
|
R.compose(
|
||||||
|
R.path(['display']),
|
||||||
|
R.find(R.propEq('code', it))
|
||||||
|
)(cryptoCurrencies)
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
|
|
@ -68,13 +77,13 @@ const CryptoBalanceOverrides = ({ section }) => {
|
||||||
header: 'Cryptocurrency',
|
header: 'Cryptocurrency',
|
||||||
width: 166,
|
width: 166,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
view: R.path(['display']),
|
view: viewCrypto,
|
||||||
input: Autocomplete,
|
input: Autocomplete,
|
||||||
inputProps: {
|
inputProps: {
|
||||||
options: suggestions,
|
options: it => R.concat(suggestions, findSuggestion(it)),
|
||||||
limit: null,
|
limit: null,
|
||||||
forceShowValue: true,
|
valueProp: 'code',
|
||||||
getOptionSelected: R.eqProps('display')
|
getLabel: R.path(['display'])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,22 @@ const MACHINE_KEY = 'machine'
|
||||||
const NAME = 'fiatBalanceOverrides'
|
const NAME = 'fiatBalanceOverrides'
|
||||||
|
|
||||||
const FiatBalanceOverrides = ({ section }) => {
|
const FiatBalanceOverrides = ({ section }) => {
|
||||||
const { machines, data, save, isDisabled, setEditing } = useContext(
|
const { machines = [], data, save, isDisabled, setEditing } = useContext(
|
||||||
NotificationsCtx
|
NotificationsCtx
|
||||||
)
|
)
|
||||||
|
|
||||||
const setupValues = data?.fiatBalanceOverrides ?? []
|
const setupValues = data?.fiatBalanceOverrides ?? []
|
||||||
const innerSetEditing = it => setEditing(NAME, it)
|
const innerSetEditing = it => setEditing(NAME, it)
|
||||||
|
|
||||||
const getSuggestions = () => {
|
const overridenMachines = R.map(override => override.machine, setupValues)
|
||||||
const overridenMachines = R.map(override => override.machine, setupValues)
|
const suggestionFilter = R.filter(
|
||||||
return R.without(overridenMachines, machines ?? [])
|
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 = {
|
const initialValues = {
|
||||||
|
|
@ -44,8 +50,6 @@ const FiatBalanceOverrides = ({ section }) => {
|
||||||
.required()
|
.required()
|
||||||
})
|
})
|
||||||
|
|
||||||
const suggestions = getSuggestions()
|
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
name: MACHINE_KEY,
|
name: MACHINE_KEY,
|
||||||
|
|
@ -54,9 +58,8 @@ const FiatBalanceOverrides = ({ section }) => {
|
||||||
view: R.path(['name']),
|
view: R.path(['name']),
|
||||||
input: Autocomplete,
|
input: Autocomplete,
|
||||||
inputProps: {
|
inputProps: {
|
||||||
options: suggestions,
|
options: it => R.concat(suggestions, findSuggestion(it)),
|
||||||
limit: null,
|
limit: null,
|
||||||
forceShowValue: true,
|
|
||||||
getOptionSelected: R.eqProps('display')
|
getOptionSelected: R.eqProps('display')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
Th
|
Th
|
||||||
} from 'src/components/fake-table/Table'
|
} from 'src/components/fake-table/Table'
|
||||||
import { Switch } from 'src/components/inputs'
|
import { Switch } from 'src/components/inputs'
|
||||||
|
import { fromNamespace, toNamespace } from 'src/utils/config'
|
||||||
import { startCase } from 'src/utils/string'
|
import { startCase } from 'src/utils/string'
|
||||||
|
|
||||||
import NotificationsCtx from '../NotificationsContext'
|
import NotificationsCtx from '../NotificationsContext'
|
||||||
|
|
@ -26,12 +27,15 @@ const sizes = {
|
||||||
const width = R.sum(R.values(sizes)) + channelSize
|
const width = R.sum(R.values(sizes)) + channelSize
|
||||||
|
|
||||||
const Row = ({ namespace }) => {
|
const Row = ({ namespace }) => {
|
||||||
const { data, save } = useContext(NotificationsCtx)
|
const { data: rawData, save: rawSave } = useContext(NotificationsCtx)
|
||||||
const disabled = !data || !data[`${namespace}_active`]
|
|
||||||
|
const save = R.compose(rawSave(null), toNamespace(namespace))
|
||||||
|
const data = fromNamespace(namespace)(rawData)
|
||||||
|
|
||||||
|
const disabled = !data || !data.active
|
||||||
|
|
||||||
const Cell = ({ name, disabled }) => {
|
const Cell = ({ name, disabled }) => {
|
||||||
const namespaced = `${namespace}_${name}`
|
const value = !!(data && data[name])
|
||||||
const value = !!(data && data[namespaced])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Td width={sizes[name]} textAlign="center">
|
<Td width={sizes[name]} textAlign="center">
|
||||||
|
|
@ -39,7 +43,7 @@ const Row = ({ namespace }) => {
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
checked={value}
|
checked={value}
|
||||||
onChange={event => {
|
onChange={event => {
|
||||||
save(null, { [namespaced]: event.target.checked })
|
save({ [name]: event.target.checked })
|
||||||
}}
|
}}
|
||||||
value={value}
|
value={value}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -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 { useQuery, useMutation } from '@apollo/react-hooks'
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { gql } from 'apollo-boost'
|
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 Popper from 'src/components/Popper'
|
||||||
|
import { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable'
|
||||||
|
import { Button } from 'src/components/buttons'
|
||||||
import { Switch } from 'src/components/inputs'
|
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 { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg'
|
||||||
|
|
||||||
import { mainStyles } from './CoinATMRadar.styles'
|
import { mainStyles } from './CoinATMRadar.styles'
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { makeStyles } from '@material-ui/core'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
import Sidebar from 'src/components/Sidebar'
|
|
||||||
import Title from 'src/components/Title'
|
import Title from 'src/components/Title'
|
||||||
|
import Sidebar from 'src/components/layout/Sidebar'
|
||||||
|
|
||||||
import logsStyles from '../Logs.styles'
|
import logsStyles from '../Logs.styles'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Card
|
|
||||||
account={account}
|
|
||||||
title="BitGo (Wallet)"
|
|
||||||
items={items}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="Bitgo"
|
|
||||||
formik={formik}
|
|
||||||
code={code}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { BitgoForm, BitgoCard, getBitgoFormik, getBitgoFields }
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Card
|
|
||||||
account={account}
|
|
||||||
title="Bitstamp (Exchange)"
|
|
||||||
items={items}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="Bitstamp"
|
|
||||||
formik={formik}
|
|
||||||
code={code}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { BitstampForm, BitstampCard, getBitstampFormik, getBitstampFields }
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Card
|
|
||||||
account={account}
|
|
||||||
title="Blockcypher (Payments)"
|
|
||||||
items={items}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="Blockcypher"
|
|
||||||
code={code}
|
|
||||||
formik={formik}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
BlockcypherForm,
|
|
||||||
BlockcypherCard,
|
|
||||||
getBlockcypherFormik,
|
|
||||||
getBlockcypherFields
|
|
||||||
}
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Paper className={classes.paper}>
|
|
||||||
<button onClick={() => handleClose()}>
|
|
||||||
<CloseIcon />
|
|
||||||
</button>
|
|
||||||
<div className={classes.modalHeader}>
|
|
||||||
<H2>{`Edit ${title}`}</H2>
|
|
||||||
</div>
|
|
||||||
<div className={classes.modalBody}>
|
|
||||||
<Formik
|
|
||||||
initialValues={formik.initialValues}
|
|
||||||
validate={formik.validate}
|
|
||||||
validationSchema={formik.validationSchema}
|
|
||||||
onSubmit={values => {
|
|
||||||
save(code, values)
|
|
||||||
.then(m => handleClose())
|
|
||||||
.catch(err => {
|
|
||||||
if (err) setError(true)
|
|
||||||
})
|
|
||||||
}}>
|
|
||||||
<Form>
|
|
||||||
<div className={classes.formBody}>
|
|
||||||
{fields &&
|
|
||||||
fields.map((field, idx) => (
|
|
||||||
<div key={idx} className={classes.field}>
|
|
||||||
<Field
|
|
||||||
id={field.name}
|
|
||||||
name={field.name}
|
|
||||||
component={field.component}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
type={field.type}
|
|
||||||
label={field.label}
|
|
||||||
onFocus={() => {
|
|
||||||
setError(null)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className={classnames(submitWrapperClasses)}>
|
|
||||||
<div className={classes.messageWrapper}>
|
|
||||||
{error && (
|
|
||||||
<div>
|
|
||||||
<ErrorIcon />
|
|
||||||
<Info3 className={classes.message}>
|
|
||||||
{DEFAULT_ERROR_MESSAGE}
|
|
||||||
</Info3>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button type="submit">Save changes</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</Formik>
|
|
||||||
</div>
|
|
||||||
</Paper>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditService
|
|
||||||
67
new-lamassu-admin/src/pages/Services/FormRenderer.js
Normal file
67
new-lamassu-admin/src/pages/Services/FormRenderer.js
Normal file
|
|
@ -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 (
|
||||||
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
|
initialValues={values}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
onSubmit={save}>
|
||||||
|
<Form className={classes.form}>
|
||||||
|
<Grid container spacing={3} className={classes.grid}>
|
||||||
|
{elements.map(({ component, code, display }) => (
|
||||||
|
<Grid item xs={12} key={code}>
|
||||||
|
<FastField
|
||||||
|
component={component}
|
||||||
|
name={code}
|
||||||
|
label={display}
|
||||||
|
fullWidth={true}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
<Button className={classes.button} type="submit">
|
||||||
|
{buttonLabel}
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormRenderer
|
||||||
|
|
@ -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 (
|
|
||||||
<Card
|
|
||||||
account={account}
|
|
||||||
title="Infura (Wallet)"
|
|
||||||
items={items}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="Infura"
|
|
||||||
formik={formik}
|
|
||||||
code={code}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { InfuraCard, InfuraForm, getInfuraFormik, getInfuraFields }
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Card account={account} title="itBit ()" items={items} onEdit={onEdit} />
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="itBit"
|
|
||||||
formik={formik}
|
|
||||||
code={code}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { ItbitCard, ItbitForm, getItbitFormik, getItbitFields }
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Card
|
|
||||||
account={account}
|
|
||||||
title="Kraken (Exchange)"
|
|
||||||
items={items}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="Kraken"
|
|
||||||
formik={formik}
|
|
||||||
code={code}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { KrakenCard, KrakenForm, getKrakenFormik, getKrakenFields }
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Card
|
|
||||||
account={account}
|
|
||||||
title="Mailgun (Email)"
|
|
||||||
items={items}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="Mailgun"
|
|
||||||
formik={formik}
|
|
||||||
code={code}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { MailgunCard, MailgunForm, getMailgunFormik, getMailgunFields }
|
|
||||||
|
|
@ -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 { 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 FormRenderer from './FormRenderer'
|
||||||
import { BitstampCard, BitstampForm } from './Bitstamp'
|
import schemas from './schemas'
|
||||||
import { BlockcypherCard, BlockcypherForm } from './Blockcypher'
|
|
||||||
import { InfuraCard, InfuraForm } from './Infura'
|
const GET_INFO = gql`
|
||||||
import { ItbitCard, ItbitForm } from './Itbit'
|
query getData {
|
||||||
import { KrakenCard, KrakenForm } from './Kraken'
|
accounts
|
||||||
import { MailgunCard, MailgunForm } from './Mailgun'
|
}
|
||||||
import { StrikeCard, StrikeForm } from './Strike'
|
`
|
||||||
import { TwilioCard, TwilioForm } from './Twilio'
|
|
||||||
import { servicesStyles as styles } from './Services.styles'
|
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 useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const GET_CONFIG = gql`
|
const Services = ({ key: SCREEN_KEY }) => {
|
||||||
{
|
const [editingSchema, setEditingSchema] = useState(null)
|
||||||
config
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const GET_ACCOUNTS = gql`
|
const { data } = useQuery(GET_INFO)
|
||||||
{
|
const [saveAccount] = useMutation(SAVE_ACCOUNT, {
|
||||||
accounts {
|
onCompleted: () => setEditingSchema(null),
|
||||||
code
|
refetchQueries: ['getData']
|
||||||
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 classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
useQuery(GET_CONFIG, {
|
const accounts = data?.accounts ?? []
|
||||||
onCompleted: data => setAccountsConfig(data.config.accounts ?? {})
|
|
||||||
})
|
|
||||||
const { data: accountsResponse } = useQuery(GET_ACCOUNTS)
|
|
||||||
|
|
||||||
const accounts = accountsResponse?.accounts
|
const getValue = code => R.find(R.propEq('code', code))(accounts)
|
||||||
|
|
||||||
const save = (code, it) => {
|
const getItems = (code, elements) => {
|
||||||
const newAccounts = R.clone(accountsConfig)
|
const faceElements = R.filter(R.prop('face'))(elements)
|
||||||
newAccounts[code] = it
|
const values = getValue(code) || {}
|
||||||
return saveConfig({ variables: { config: { accounts: newAccounts } } })
|
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 (
|
return (
|
||||||
<>
|
<div className={classes.wrapper}>
|
||||||
<div className={classes.titleWrapper}>
|
<TitleSection title="3rd Party Services" />
|
||||||
<div className={classes.titleContainer}>
|
<Grid container spacing={4}>
|
||||||
<Title>3rd Party Services</Title>
|
{R.values(schemas).map(schema => (
|
||||||
</div>
|
<Grid item key={schema.code}>
|
||||||
</div>
|
<SingleRowTable
|
||||||
<div className={classes.mainWrapper}>
|
title={schema.title}
|
||||||
<BitgoCard
|
onEdit={() => setEditingSchema(schema)}
|
||||||
account={bitgo}
|
items={getItems(schema.code, schema.elements)}
|
||||||
onEdit={() =>
|
/>
|
||||||
handleOpen(
|
</Grid>
|
||||||
<BitgoForm
|
))}
|
||||||
account={bitgo}
|
</Grid>
|
||||||
handleClose={handleClose}
|
{editingSchema && (
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<BitstampCard
|
|
||||||
account={bitstamp}
|
|
||||||
onEdit={() =>
|
|
||||||
handleOpen(
|
|
||||||
<BitstampForm
|
|
||||||
account={bitstamp}
|
|
||||||
handleClose={handleClose}
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<BlockcypherCard
|
|
||||||
account={blockcypher}
|
|
||||||
onEdit={() =>
|
|
||||||
handleOpen(
|
|
||||||
<BlockcypherForm
|
|
||||||
account={blockcypher}
|
|
||||||
handleClose={handleClose}
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<InfuraCard
|
|
||||||
account={infura}
|
|
||||||
onEdit={() =>
|
|
||||||
handleOpen(
|
|
||||||
<InfuraForm
|
|
||||||
account={infura}
|
|
||||||
handleClose={handleClose}
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ItbitCard
|
|
||||||
account={itbit}
|
|
||||||
onEdit={() =>
|
|
||||||
handleOpen(
|
|
||||||
<ItbitForm
|
|
||||||
account={itbit}
|
|
||||||
handleClose={handleClose}
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<KrakenCard
|
|
||||||
account={kraken}
|
|
||||||
onEdit={() =>
|
|
||||||
handleOpen(
|
|
||||||
<KrakenForm
|
|
||||||
account={kraken}
|
|
||||||
handleClose={handleClose}
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<MailgunCard
|
|
||||||
account={mailgun}
|
|
||||||
onEdit={() =>
|
|
||||||
handleOpen(
|
|
||||||
<MailgunForm
|
|
||||||
account={mailgun}
|
|
||||||
handleClose={handleClose}
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<StrikeCard
|
|
||||||
account={strike}
|
|
||||||
onEdit={() =>
|
|
||||||
handleOpen(
|
|
||||||
<StrikeForm
|
|
||||||
account={strike}
|
|
||||||
handleClose={handleClose}
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<TwilioCard
|
|
||||||
account={twilio}
|
|
||||||
onEdit={() =>
|
|
||||||
handleOpen(
|
|
||||||
<TwilioForm
|
|
||||||
account={twilio}
|
|
||||||
handleClose={handleClose}
|
|
||||||
save={save}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{modalContent && (
|
|
||||||
<Modal
|
<Modal
|
||||||
aria-labelledby="simple-modal-title"
|
title={`Edit ${editingSchema.name}`}
|
||||||
aria-describedby="simple-modal-description"
|
width={478}
|
||||||
open={open}
|
handleClose={() => setEditingSchema(null)}
|
||||||
onClose={handleClose}
|
open={true}>
|
||||||
className={classes.modal}>
|
<FormRenderer
|
||||||
<div>{modalContent}</div>
|
save={it =>
|
||||||
|
saveAccount({
|
||||||
|
variables: { account: { code: editingSchema.code, ...it } }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
elements={editingSchema.elements}
|
||||||
|
validationSchema={editingSchema.validationSchema}
|
||||||
|
value={getValue(editingSchema.code)}
|
||||||
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Card
|
|
||||||
account={account}
|
|
||||||
title="Strike (Lightning Payments)"
|
|
||||||
items={items}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="Strike"
|
|
||||||
formik={formik}
|
|
||||||
code={code}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { StrikeCard, StrikeForm, getStrikeFormik, getStrikeFields }
|
|
||||||
|
|
@ -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 (
|
|
||||||
<Card
|
|
||||||
account={account}
|
|
||||||
title="Twilio (SMS)"
|
|
||||||
items={items}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<EditService
|
|
||||||
title="Twilio"
|
|
||||||
formik={formik}
|
|
||||||
code={code}
|
|
||||||
fields={fields}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { TwilioCard, TwilioForm, getTwilioFormik, getTwilioFields }
|
|
||||||
|
|
@ -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 (
|
|
||||||
<SingleRowTable
|
|
||||||
title={title}
|
|
||||||
items={items}
|
|
||||||
className={classes.card}
|
|
||||||
onEdit={onEdit}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Card, getValue, formatLong }
|
|
||||||
120
new-lamassu-admin/src/pages/Services/schemas/bitgo.js
Normal file
120
new-lamassu-admin/src/pages/Services/schemas/bitgo.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
43
new-lamassu-admin/src/pages/Services/schemas/bitstamp.js
Normal file
43
new-lamassu-admin/src/pages/Services/schemas/bitstamp.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
34
new-lamassu-admin/src/pages/Services/schemas/blockcypher.js
Normal file
34
new-lamassu-admin/src/pages/Services/schemas/blockcypher.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
21
new-lamassu-admin/src/pages/Services/schemas/index.js
Normal file
21
new-lamassu-admin/src/pages/Services/schemas/index.js
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
41
new-lamassu-admin/src/pages/Services/schemas/infura.js
Normal file
41
new-lamassu-admin/src/pages/Services/schemas/infura.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
50
new-lamassu-admin/src/pages/Services/schemas/itbit.js
Normal file
50
new-lamassu-admin/src/pages/Services/schemas/itbit.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
32
new-lamassu-admin/src/pages/Services/schemas/kraken.js
Normal file
32
new-lamassu-admin/src/pages/Services/schemas/kraken.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
49
new-lamassu-admin/src/pages/Services/schemas/mailgun.js
Normal file
49
new-lamassu-admin/src/pages/Services/schemas/mailgun.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
23
new-lamassu-admin/src/pages/Services/schemas/strike.js
Normal file
23
new-lamassu-admin/src/pages/Services/schemas/strike.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
48
new-lamassu-admin/src/pages/Services/schemas/twilio.js
Normal file
48
new-lamassu-admin/src/pages/Services/schemas/twilio.js
Normal file
|
|
@ -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')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import React, { useState } from 'react'
|
||||||
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
|
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
|
||||||
import Title from 'src/components/Title'
|
import Title from 'src/components/Title'
|
||||||
import { FeatureButton } from 'src/components/buttons'
|
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 DownloadInverseIcon } from 'src/styling/icons/button/download/white.svg'
|
||||||
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.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'
|
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
|
|
@ -71,29 +71,34 @@ const Transactions = () => {
|
||||||
{
|
{
|
||||||
header: '',
|
header: '',
|
||||||
width: 62,
|
width: 62,
|
||||||
|
size: 'sm',
|
||||||
view: it => (it.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />)
|
view: it => (it.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Machine',
|
header: 'Machine',
|
||||||
name: 'machineName',
|
name: 'machineName',
|
||||||
width: 180,
|
width: 180,
|
||||||
|
size: 'sm',
|
||||||
view: R.path(['machineName'])
|
view: R.path(['machineName'])
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Customer',
|
header: 'Customer',
|
||||||
width: 162,
|
width: 162,
|
||||||
|
size: 'sm',
|
||||||
view: getCustomerDisplayName
|
view: getCustomerDisplayName
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Cash',
|
header: 'Cash',
|
||||||
width: 110,
|
width: 110,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
|
size: 'sm',
|
||||||
view: it => `${Number.parseFloat(it.fiat)} ${it.fiatCode}`
|
view: it => `${Number.parseFloat(it.fiat)} ${it.fiatCode}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Crypto',
|
header: 'Crypto',
|
||||||
width: 141,
|
width: 141,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
|
size: 'sm',
|
||||||
view: it =>
|
view: it =>
|
||||||
`${toUnit(new BigNumber(it.cryptoAtoms), it.cryptoCode).toFormat(5)} ${
|
`${toUnit(new BigNumber(it.cryptoAtoms), it.cryptoCode).toFormat(5)} ${
|
||||||
it.cryptoCode
|
it.cryptoCode
|
||||||
|
|
@ -103,27 +108,22 @@ const Transactions = () => {
|
||||||
header: 'Address',
|
header: 'Address',
|
||||||
view: R.path(['toAddress']),
|
view: R.path(['toAddress']),
|
||||||
className: classes.overflowTd,
|
className: classes.overflowTd,
|
||||||
|
size: 'sm',
|
||||||
width: 136
|
width: 136
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Date (UTC)',
|
header: 'Date (UTC)',
|
||||||
view: it => moment.utc(it.created).format('YYYY-MM-D'),
|
view: it => moment.utc(it.created).format('YYYY-MM-D'),
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
|
size: 'sm',
|
||||||
width: 124
|
width: 124
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Time (UTC)',
|
header: 'Time (UTC)',
|
||||||
view: it => moment.utc(it.created).format('HH:mm:ss'),
|
view: it => moment.utc(it.created).format('HH:mm:ss'),
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
|
size: 'sm',
|
||||||
width: 124
|
width: 124
|
||||||
},
|
|
||||||
{
|
|
||||||
header: '', // Trade
|
|
||||||
view: () => {},
|
|
||||||
width: 90
|
|
||||||
},
|
|
||||||
{
|
|
||||||
width: 71
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -176,10 +176,11 @@ const Transactions = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ExpTable
|
<DataTable
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={R.path(['transactions'])(txResponse)}
|
data={R.path(['transactions'])(txResponse)}
|
||||||
Details={DetailsRow}
|
Details={DetailsRow}
|
||||||
|
expandable
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
99
new-lamassu-admin/src/pages/Wallet/Wallet.js
Normal file
99
new-lamassu-admin/src/pages/Wallet/Wallet.js
Normal file
|
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<TitleSection title="Wallet Settings" error={error} />
|
||||||
|
<EditableTable
|
||||||
|
name="test"
|
||||||
|
namespaces={R.map(R.path(['code']))(cryptoCurrencies)}
|
||||||
|
data={config}
|
||||||
|
stripeWhen={it => !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 && (
|
||||||
|
<Wizard
|
||||||
|
coin={R.find(R.propEq('code', wizard))(cryptoCurrencies)}
|
||||||
|
onClose={() => setWizard(false)}
|
||||||
|
save={save}
|
||||||
|
error={error}
|
||||||
|
cryptoCurrencies={cryptoCurrencies}
|
||||||
|
userAccounts={data?.config?.accounts}
|
||||||
|
accounts={accounts}
|
||||||
|
accountsConfig={accountsConfig}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Wallet
|
||||||
|
|
@ -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(
|
|
||||||
<WizardSplash
|
|
||||||
code={row[CRYPTOCURRENCY_KEY]}
|
|
||||||
coinName={getCryptoDisplayName(row)}
|
|
||||||
handleModalNavigation={handleModalNavigation(row)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
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(
|
|
||||||
<Wizard
|
|
||||||
crypto={row}
|
|
||||||
coinName={getCryptoDisplayName(row)}
|
|
||||||
handleModalNavigation={handleModalNavigation}
|
|
||||||
pageName={TICKER_KEY}
|
|
||||||
currentStage={1}
|
|
||||||
alreadySetUp={R.filter(
|
|
||||||
ticker => R.includes(cryptocode, ticker.cryptos),
|
|
||||||
R.filter(R.propEq('class', 'ticker'), accounts)
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
setModalContent(
|
|
||||||
<Wizard
|
|
||||||
crypto={row}
|
|
||||||
coinName={getCryptoDisplayName(row)}
|
|
||||||
handleModalNavigation={handleModalNavigation}
|
|
||||||
pageName={WALLET_KEY}
|
|
||||||
currentStage={2}
|
|
||||||
alreadySetUp={getAlreadySetUp(WALLET_KEY)(cryptocode)}
|
|
||||||
notSetUp={getNotSetUp(WALLET_KEY)(cryptocode)}
|
|
||||||
saveNewService={saveNewService}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
setModalContent(
|
|
||||||
<Wizard
|
|
||||||
crypto={row}
|
|
||||||
coinName={getCryptoDisplayName(row)}
|
|
||||||
handleModalNavigation={handleModalNavigation}
|
|
||||||
pageName={EXCHANGE_KEY}
|
|
||||||
currentStage={3}
|
|
||||||
alreadySetUp={getAlreadySetUp(EXCHANGE_KEY)(cryptocode)}
|
|
||||||
notSetUp={getNotSetUp(EXCHANGE_KEY)(cryptocode)}
|
|
||||||
saveNewService={saveNewService}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
setModalContent(
|
|
||||||
<Wizard
|
|
||||||
crypto={row}
|
|
||||||
coinName={getCryptoDisplayName(row)}
|
|
||||||
handleModalNavigation={handleModalNavigation}
|
|
||||||
pageName={ZERO_CONF_KEY}
|
|
||||||
currentStage={4}
|
|
||||||
alreadySetUp={getAlreadySetUp(ZERO_CONF_KEY)(cryptocode)}
|
|
||||||
notSetUp={getNotSetUp(ZERO_CONF_KEY)(cryptocode)}
|
|
||||||
saveNewService={saveNewService}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
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 (
|
|
||||||
<>
|
|
||||||
<div className={classes.titleWrapper}>
|
|
||||||
<div className={classes.titleAndButtonsContainer}>
|
|
||||||
<Title>Wallet Settings</Title>
|
|
||||||
{error && !modalOpen && (
|
|
||||||
<ErrorMessage className={classes.switchErrorMessage}>
|
|
||||||
Failed to save
|
|
||||||
</ErrorMessage>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={classes.wrapper}>
|
|
||||||
<Table>
|
|
||||||
<THead>
|
|
||||||
<Th
|
|
||||||
size={getSize(CRYPTOCURRENCY_KEY)}
|
|
||||||
textAlign={getTextAlign(CRYPTOCURRENCY_KEY)}>
|
|
||||||
Cryptocurrency
|
|
||||||
</Th>
|
|
||||||
<Th size={getSize(TICKER_KEY)} textAlign={getTextAlign(TICKER_KEY)}>
|
|
||||||
Ticker
|
|
||||||
</Th>
|
|
||||||
<Th size={getSize(WALLET_KEY)} textAlign={getTextAlign(WALLET_KEY)}>
|
|
||||||
Wallet
|
|
||||||
</Th>
|
|
||||||
<Th
|
|
||||||
size={getSize(EXCHANGE_KEY)}
|
|
||||||
textAlign={getTextAlign(EXCHANGE_KEY)}>
|
|
||||||
Exchange
|
|
||||||
</Th>
|
|
||||||
<Th
|
|
||||||
size={getSize(ZERO_CONF_KEY)}
|
|
||||||
textAlign={getTextAlign(ZERO_CONF_KEY)}>
|
|
||||||
Zero Conf
|
|
||||||
</Th>
|
|
||||||
<Th size={getSize(EDIT_KEY)} textAlign={getTextAlign(EDIT_KEY)}>
|
|
||||||
Edit
|
|
||||||
</Th>
|
|
||||||
<Th size={getSize(ENABLE_KEY)} textAlign={getTextAlign(ENABLE_KEY)}>
|
|
||||||
Enable
|
|
||||||
</Th>
|
|
||||||
</THead>
|
|
||||||
<TBody>
|
|
||||||
{state.map((row, idx) => (
|
|
||||||
<Tr key={idx}>
|
|
||||||
<Td
|
|
||||||
size={getSize(CRYPTOCURRENCY_KEY)}
|
|
||||||
textAlign={getTextAlign(CRYPTOCURRENCY_KEY)}>
|
|
||||||
{getCryptoDisplayName(row)}
|
|
||||||
</Td>
|
|
||||||
{!isSet(row) && (
|
|
||||||
<Td
|
|
||||||
size={
|
|
||||||
getSize(TICKER_KEY) +
|
|
||||||
getSize(WALLET_KEY) +
|
|
||||||
getSize(EXCHANGE_KEY) +
|
|
||||||
getSize(ZERO_CONF_KEY)
|
|
||||||
}
|
|
||||||
textAlign="center"
|
|
||||||
className={classes.disabledDrawing}>
|
|
||||||
<div />
|
|
||||||
</Td>
|
|
||||||
)}
|
|
||||||
{isSet(row) && (
|
|
||||||
<>
|
|
||||||
<Td
|
|
||||||
size={getSize(TICKER_KEY)}
|
|
||||||
textAlign={getTextAlign(TICKER_KEY)}>
|
|
||||||
{getDisplayName(accounts)(row[TICKER_KEY])}
|
|
||||||
</Td>
|
|
||||||
<Td
|
|
||||||
size={getSize(WALLET_KEY)}
|
|
||||||
textAlign={getTextAlign(WALLET_KEY)}>
|
|
||||||
{getDisplayName(accounts)(row[WALLET_KEY])}
|
|
||||||
</Td>
|
|
||||||
<Td
|
|
||||||
size={getSize(EXCHANGE_KEY)}
|
|
||||||
textAlign={getTextAlign(EXCHANGE_KEY)}>
|
|
||||||
{getDisplayName(accounts)(row[EXCHANGE_KEY])}
|
|
||||||
</Td>
|
|
||||||
<Td
|
|
||||||
size={getSize(ZERO_CONF_KEY)}
|
|
||||||
textAlign={getTextAlign(ZERO_CONF_KEY)}>
|
|
||||||
{getDisplayName(accounts)(row[ZERO_CONF_KEY])}
|
|
||||||
</Td>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Td size={getSize(EDIT_KEY)} textAlign={getTextAlign(EDIT_KEY)}>
|
|
||||||
{!isSet(row) && <DisabledEditIcon />}
|
|
||||||
{isSet(row) && (
|
|
||||||
<button
|
|
||||||
className={classes.iconButton}
|
|
||||||
onClick={() => handleEditClick(row)}>
|
|
||||||
<EditIcon />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
<Td
|
|
||||||
size={getSize(ENABLE_KEY)}
|
|
||||||
textAlign={getTextAlign(ENABLE_KEY)}>
|
|
||||||
<Switch
|
|
||||||
checked={row[ENABLE_KEY]}
|
|
||||||
onChange={handleEnable(row)}
|
|
||||||
value={row[CRYPTOCURRENCY_KEY]}
|
|
||||||
/>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
))}
|
|
||||||
</TBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
<Modal
|
|
||||||
aria-labelledby="simple-modal-title"
|
|
||||||
aria-describedby="simple-modal-description"
|
|
||||||
open={modalOpen}
|
|
||||||
handleClose={handleModalClose}
|
|
||||||
className={classes.modal}>
|
|
||||||
{modalContent}
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WalletSettings
|
|
||||||
|
|
@ -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 * as R from 'ramda'
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
import ErrorMessage from 'src/components/ErrorMessage'
|
import Modal from 'src/components/Modal'
|
||||||
import Stage from 'src/components/Stage'
|
import schema from 'src/pages/Services/schemas'
|
||||||
import { Button } from 'src/components/buttons'
|
import { toNamespace } from 'src/utils/config'
|
||||||
import { RadioGroup, AutocompleteSelect } from 'src/components/inputs'
|
|
||||||
import { H1, Info2, H4 } from 'src/components/typography'
|
|
||||||
import { startCase } from 'src/utils/string'
|
|
||||||
|
|
||||||
import { getBitgoFields, getBitgoFormik } from '../Services/Bitgo'
|
import WizardSplash from './WizardSplash'
|
||||||
import { getBitstampFields, getBitstampFormik } from '../Services/Bitstamp'
|
import WizardStep from './WizardStep'
|
||||||
import {
|
|
||||||
getBlockcypherFields,
|
|
||||||
getBlockcypherFormik
|
|
||||||
} from '../Services/Blockcypher'
|
|
||||||
import { getInfuraFields, getInfuraFormik } from '../Services/Infura'
|
|
||||||
import { getKrakenFields, getKrakenFormik } from '../Services/Kraken'
|
|
||||||
import { getStrikeFields, getStrikeFormik } from '../Services/Strike'
|
|
||||||
|
|
||||||
const styles = {
|
const LAST_STEP = 4
|
||||||
modalContent: {
|
const MODAL_WIDTH = 554
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
const contains = crypto => R.compose(R.contains(crypto), R.prop('cryptos'))
|
||||||
padding: [[24, 32, 0]],
|
const sameClass = type => R.propEq('class', type)
|
||||||
'& > h1': {
|
const filterConfig = (crypto, type) =>
|
||||||
margin: [[0, 0, 10]]
|
R.filter(it => sameClass(type)(it) && contains(crypto)(it))
|
||||||
},
|
|
||||||
'& > h4': {
|
const getItems = (accountsConfig, accounts, type, crypto) => {
|
||||||
margin: [[32, 0, 32 - 9, 0]]
|
const fConfig = filterConfig(crypto, type)(accountsConfig)
|
||||||
},
|
const find = code => R.find(R.propEq('code', code))(accounts)
|
||||||
'& > p': {
|
|
||||||
margin: 0
|
const [filled, unfilled] = R.partition(({ code }) => {
|
||||||
}
|
const account = find(code)
|
||||||
},
|
if (!schema[code]) return true
|
||||||
submitButtonWrapper: {
|
|
||||||
display: 'flex',
|
const { validationSchema } = schema[code]
|
||||||
alignSelf: 'flex-end',
|
return validationSchema.isValidSync(account)
|
||||||
margin: [['auto', 0, 0]]
|
})(fConfig)
|
||||||
},
|
|
||||||
submitButton: {
|
return { filled, unfilled }
|
||||||
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 getNewServiceForm = serviceName => {
|
const Wizard = ({ coin, onClose, accountsConfig, accounts, save, error }) => {
|
||||||
switch (serviceName) {
|
const [{ step, config, accountsToSave }, setState] = useState({
|
||||||
case 'bitgo':
|
step: 0,
|
||||||
return { fields: getBitgoFields(), formik: getBitgoFormik() }
|
config: { active: true },
|
||||||
case 'bitstamp':
|
accountsToSave: []
|
||||||
return { fields: getBitstampFields(), formik: getBitstampFormik() }
|
})
|
||||||
case 'blockcypher':
|
|
||||||
return { fields: getBlockcypherFields(), formik: getBlockcypherFormik() }
|
const title = `Enable ${coin.display}`
|
||||||
case 'infura':
|
const isLastStep = step === LAST_STEP
|
||||||
return { fields: getInfuraFields(), formik: getInfuraFormik() }
|
|
||||||
case 'kraken':
|
const tickers = { filled: filterConfig(coin.code, 'ticker')(accountsConfig) }
|
||||||
return { fields: getKrakenFields(), formik: getKrakenFormik() }
|
const wallets = getItems(accountsConfig, accounts, 'wallet', coin.code)
|
||||||
case 'strike':
|
const exchanges = getItems(accountsConfig, accounts, 'exchange', coin.code)
|
||||||
return { fields: getStrikeFields(), formik: getStrikeFormik() }
|
const zeroConfs = getItems(accountsConfig, accounts, 'zeroConf', coin.code)
|
||||||
default:
|
|
||||||
|
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 getStepData = () => {
|
||||||
|
switch (step) {
|
||||||
const SubmitButton = ({ error, ...props }) => {
|
case 1:
|
||||||
const classes = useStyles()
|
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 (
|
return (
|
||||||
<div className={classes.submitButtonWrapper}>
|
<Modal
|
||||||
{error && <ErrorMessage>Failed to save</ErrorMessage>}
|
title={step === 0 ? null : title}
|
||||||
<Button {...props}>Next</Button>
|
handleClose={onClose}
|
||||||
</div>
|
width={MODAL_WIDTH}
|
||||||
)
|
open={true}>
|
||||||
}
|
{step === 0 && (
|
||||||
|
<WizardSplash
|
||||||
const Wizard = ({
|
code={coin.code}
|
||||||
crypto,
|
name={coin.display}
|
||||||
coinName,
|
onContinue={() => onContinue()}
|
||||||
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 (
|
|
||||||
<div className={classes.modalContent}>
|
|
||||||
<H1>Enable {coinName}</H1>
|
|
||||||
<Info2>{startCase(pageName)}</Info2>
|
|
||||||
<Stage
|
|
||||||
stages={4}
|
|
||||||
currentStage={currentStage}
|
|
||||||
color="spring"
|
|
||||||
className={classes.stages}
|
|
||||||
/>
|
|
||||||
<H4>{`Select a ${pageName} or set up a new one`}</H4>
|
|
||||||
<div className={classnames(radiosClassNames)}>
|
|
||||||
{alreadySetUp && (
|
|
||||||
<RadioGroup
|
|
||||||
name="already-setup-select"
|
|
||||||
value={selectedRadio || radioButtonOptions[0]}
|
|
||||||
options={radioButtonOptions}
|
|
||||||
ariaLabel="already-setup-select"
|
|
||||||
onChange={handleRadioButtons}
|
|
||||||
className={classes.alreadySetupRadioButtons}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{notSetUp && (
|
|
||||||
<div className={classes.selectNewWrapper}>
|
|
||||||
<RadioGroup
|
|
||||||
name="setup-new-select"
|
|
||||||
value={setUpNew || ''}
|
|
||||||
options={[{ label: 'Set up new', value: 'new' }]}
|
|
||||||
ariaLabel="setup-new-select"
|
|
||||||
onChange={handleSetUpNew}
|
|
||||||
className={classes.alreadySetupRadioButtons}
|
|
||||||
/>
|
|
||||||
{setUpNew && (
|
|
||||||
<AutocompleteSelect
|
|
||||||
id="chooseNew"
|
|
||||||
name="chooseNew"
|
|
||||||
label={`Select ${pageName}`}
|
|
||||||
suggestions={notSetUp}
|
|
||||||
value={selectedFromDropdown}
|
|
||||||
handleChange={handleSelectFromDropdown}
|
|
||||||
className={classes.selectNew}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{formContent && (
|
|
||||||
<Formik
|
|
||||||
initialValues={formContent.formik.initialValues}
|
|
||||||
validationSchema={formContent.formik.validationSchema}
|
|
||||||
onSubmit={values =>
|
|
||||||
saveNewService(selectedFromDropdown.code, values)
|
|
||||||
.then(m => {
|
|
||||||
handleNext(selectedFromDropdown.code)()
|
|
||||||
})
|
|
||||||
.catch(error => setError(error))
|
|
||||||
}>
|
|
||||||
{props => (
|
|
||||||
<form
|
|
||||||
onReset={props.handleReset}
|
|
||||||
onSubmit={props.handleSubmit}
|
|
||||||
className={classes.newServiceForm}
|
|
||||||
{...props}>
|
|
||||||
<div className={classes.newServiceFormFields}>
|
|
||||||
{formContent.fields.map((field, idx) => (
|
|
||||||
<div key={idx} className={classes.field}>
|
|
||||||
<FormikField
|
|
||||||
id={field.name}
|
|
||||||
name={field.name}
|
|
||||||
component={field.component}
|
|
||||||
placeholder={field.placeholder}
|
|
||||||
type={field.type}
|
|
||||||
label={field.label}
|
|
||||||
className={classes.formInput}
|
|
||||||
onFocus={() => {
|
|
||||||
setError(null)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<SubmitButton
|
|
||||||
disabled={R.isEmpty(props.touched) || !props.isValid}
|
|
||||||
className={classes.submitButton}
|
|
||||||
type="submit"
|
|
||||||
error={error}
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
)}
|
|
||||||
{!formContent && (
|
|
||||||
<SubmitButton
|
|
||||||
className={classes.submitButton}
|
|
||||||
disabled={!isSubmittable()}
|
|
||||||
onClick={handleNext(selectedRadio || selectedFromDropdown?.code)}
|
|
||||||
error={error}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
{step !== 0 && (
|
||||||
|
<WizardStep
|
||||||
|
step={step}
|
||||||
|
error={error}
|
||||||
|
lastStep={isLastStep}
|
||||||
|
{...getStepData()}
|
||||||
|
onContinue={onContinue}
|
||||||
|
getValue={getValue}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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'
|
import { ReactComponent as ZCashLogo } from 'src/styling/logos/icon-zcash-colour.svg'
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
logoWrapper: {
|
logo: {
|
||||||
display: 'flex',
|
maxHeight: 80,
|
||||||
justifyContent: 'center',
|
maxWidth: 200
|
||||||
alignItems: 'center',
|
},
|
||||||
height: 80,
|
title: {
|
||||||
margin: [[40, 0, 24]],
|
margin: [[24, 0, 32, 0]]
|
||||||
'& > svg': {
|
},
|
||||||
maxHeight: '100%',
|
text: {
|
||||||
width: '100%'
|
margin: 0
|
||||||
}
|
},
|
||||||
|
button: {
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 58
|
||||||
},
|
},
|
||||||
modalContent: {
|
modalContent: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: [[0, 66]],
|
padding: [[0, 42]],
|
||||||
'& > h1': {
|
flex: 1
|
||||||
margin: [[0, 0, 32]]
|
|
||||||
},
|
|
||||||
'& > p': {
|
|
||||||
margin: 0
|
|
||||||
},
|
|
||||||
'& > button': {
|
|
||||||
margin: [['auto', 0, 56]],
|
|
||||||
'&:active': {
|
|
||||||
margin: [['auto', 0, 56]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const renderLogo = code => {
|
const getLogo = code => {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 'BTC':
|
case 'BTC':
|
||||||
return <BitcoinLogo />
|
return BitcoinLogo
|
||||||
case 'BCH':
|
case 'BCH':
|
||||||
return <BitcoinCashLogo />
|
return BitcoinCashLogo
|
||||||
case 'DASH':
|
case 'DASH':
|
||||||
return <DashLogo />
|
return DashLogo
|
||||||
case 'ETH':
|
case 'ETH':
|
||||||
return <EthereumLogo />
|
return EthereumLogo
|
||||||
case 'LTC':
|
case 'LTC':
|
||||||
return <LitecoinLogo />
|
return LitecoinLogo
|
||||||
case 'ZEC':
|
case 'ZEC':
|
||||||
return <ZCashLogo />
|
return ZCashLogo
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const WizardSplash = ({ code, coinName, handleModalNavigation }) => {
|
const WizardSplash = ({ code, name, onContinue }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
const Logo = getLogo(code)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.modalContent}>
|
<div className={classes.modalContent}>
|
||||||
<div className={classes.logoWrapper}>{renderLogo(code)}</div>
|
<Logo className={classes.logo} />
|
||||||
<H1>Enable {coinName}</H1>
|
<H1 className={classes.title}>Enable {name}</H1>
|
||||||
<P>
|
<P className={classes.text}>
|
||||||
You are about to enable {coinName} on your system. This will allow you
|
You are about to enable {name} on your system. This will allow you to
|
||||||
to use this cryptocurrency on your machines. To able to do that, you’ll
|
use this cryptocurrency on your machines. To be able to do that, you’ll
|
||||||
have to setup all the necessary 3rd party services.
|
have to setup all the necessary 3rd party services.
|
||||||
</P>
|
</P>
|
||||||
<Button onClick={() => handleModalNavigation(1)}>
|
<Button className={classes.button} onClick={onContinue}>
|
||||||
Start configuration
|
Start configuration
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
155
new-lamassu-admin/src/pages/Wallet/WizardStep.js
Normal file
155
new-lamassu-admin/src/pages/Wallet/WizardStep.js
Normal file
|
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<Info2 className={classes.title}>{startCase(type)}</Info2>
|
||||||
|
<Stepper steps={4} currentStep={step} />
|
||||||
|
<H4 className={classnames(subtitleClass)}>
|
||||||
|
Select a {displayName} or set up a new one
|
||||||
|
</H4>
|
||||||
|
<RadioGroup
|
||||||
|
options={filled}
|
||||||
|
value={selected}
|
||||||
|
className={classes.radioGroup}
|
||||||
|
onChange={(evt, it) => {
|
||||||
|
dispatch({ type: 'select', selected: it })
|
||||||
|
}}
|
||||||
|
labelClassName={classes.radioLabel}
|
||||||
|
radioClassName={classes.radio}
|
||||||
|
/>
|
||||||
|
<div className={classes.setupNew}>
|
||||||
|
{!R.isEmpty(unfilled) && !R.isNil(unfilled) && (
|
||||||
|
<RadioGroup
|
||||||
|
value={isNew}
|
||||||
|
onChange={(evt, it) => {
|
||||||
|
dispatch({ type: 'new' })
|
||||||
|
}}
|
||||||
|
labelClassName={classes.radioLabel}
|
||||||
|
radioClassName={classes.radio}
|
||||||
|
options={[{ display: 'Set up new', code: true }]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isNew && (
|
||||||
|
<Autocomplete
|
||||||
|
fullWidth
|
||||||
|
label={`Select ${displayName}`}
|
||||||
|
className={classes.picker}
|
||||||
|
getOptionSelected={R.eqProps('code')}
|
||||||
|
getLabel={R.path(['display'])}
|
||||||
|
options={unfilled}
|
||||||
|
onChange={(evt, it) => {
|
||||||
|
dispatch({ type: 'form', form: it })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{form && (
|
||||||
|
<FormRenderer
|
||||||
|
save={it =>
|
||||||
|
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 && (
|
||||||
|
<div className={classes.submit}>
|
||||||
|
{error && <ErrorMessage>Failed to save</ErrorMessage>}
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
onClick={() => iContinue({ [type]: selected })}>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WizardStep
|
||||||
42
new-lamassu-admin/src/pages/Wallet/WizardStep.styles.js
Normal file
42
new-lamassu-admin/src/pages/Wallet/WizardStep.styles.js
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
103
new-lamassu-admin/src/pages/Wallet/helper.js
Normal file
103
new-lamassu-admin/src/pages/Wallet/helper.js
Normal file
|
|
@ -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 }
|
||||||
|
|
@ -5,9 +5,10 @@ import moment from 'moment'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
|
|
||||||
import { MainStatus } from '../../components/Status'
|
import { MainStatus } from '../../components/Status'
|
||||||
import Title from '../../components/Title'
|
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 WarningIcon } from '../../styling/icons/status/pumpkin.svg'
|
||||||
import { ReactComponent as ErrorIcon } from '../../styling/icons/status/tomato.svg'
|
import { ReactComponent as ErrorIcon } from '../../styling/icons/status/tomato.svg'
|
||||||
import { mainStyles } from '../Transactions/Transactions.styles'
|
import { mainStyles } from '../Transactions/Transactions.styles'
|
||||||
|
|
@ -42,35 +43,37 @@ const MachineStatus = () => {
|
||||||
{
|
{
|
||||||
header: 'Machine Name',
|
header: 'Machine Name',
|
||||||
width: 232,
|
width: 232,
|
||||||
|
size: 'sm',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
view: m => m.name
|
view: m => m.name
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Status',
|
header: 'Status',
|
||||||
width: 349,
|
width: 349,
|
||||||
|
size: 'sm',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
view: m => <MainStatus statuses={m.statuses} />
|
view: m => <MainStatus statuses={m.statuses} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Last ping',
|
header: 'Last ping',
|
||||||
width: 192,
|
width: 192,
|
||||||
|
size: 'sm',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
view: m => moment(m.lastPing).fromNow()
|
view: m => moment(m.lastPing).fromNow()
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Ping Time',
|
header: 'Ping Time',
|
||||||
width: 155,
|
width: 155,
|
||||||
|
size: 'sm',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
view: m => m.pingTime || 'unknown'
|
view: m => m.pingTime || 'unknown'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Software Version',
|
header: 'Software Version',
|
||||||
width: 201,
|
width: 201,
|
||||||
|
size: 'sm',
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
view: m => m.softwareVersion || 'unknown'
|
view: m => m.softwareVersion || 'unknown'
|
||||||
},
|
|
||||||
{
|
|
||||||
width: 71
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -91,10 +94,11 @@ const MachineStatus = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ExpTable
|
<DataTable
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={R.path(['machines'])(machinesResponse)}
|
data={R.path(['machines'])(machinesResponse)}
|
||||||
Details={MachineDetailsRow}
|
Details={MachineDetailsRow}
|
||||||
|
expandable
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo'
|
||||||
import ServerLogs from 'src/pages/ServerLogs'
|
import ServerLogs from 'src/pages/ServerLogs'
|
||||||
import Services from 'src/pages/Services/Services'
|
import Services from 'src/pages/Services/Services'
|
||||||
import Transactions from 'src/pages/Transactions/Transactions'
|
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'
|
import MachineStatus from 'src/pages/maintenance/MachineStatus'
|
||||||
|
|
||||||
const tree = [
|
const tree = [
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import extendJss from 'jss-plugin-extend'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { ActionButton, Button, Link } from 'src/components/buttons'
|
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 AuthorizeIconReversed } from 'src/styling/icons/button/authorize/white.svg'
|
||||||
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg'
|
import { ReactComponent as AuthorizeIcon } from 'src/styling/icons/button/authorize/zodiac.svg'
|
||||||
|
|
||||||
|
|
@ -178,8 +178,6 @@ story.add('ConfirmDialog', () => (
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
))
|
))
|
||||||
|
|
||||||
story.add('Radio', () => <Radio label="Hehe" />)
|
|
||||||
|
|
||||||
const typographyStory = storiesOf('Typography', module)
|
const typographyStory = storiesOf('Typography', module)
|
||||||
typographyStory.add('H1', () => <H1>Hehehe</H1>)
|
typographyStory.add('H1', () => <H1>Hehehe</H1>)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@ export default {
|
||||||
},
|
},
|
||||||
'button::-moz-focus-inner': {
|
'button::-moz-focus-inner': {
|
||||||
border: 0
|
border: 0
|
||||||
|
},
|
||||||
|
// forcing styling onto inner container
|
||||||
|
'.ReactVirtualized__Grid__innerScrollContainer': {
|
||||||
|
overflow: 'inherit !important'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
new-lamassu-admin/src/styling/icons/stripes.svg
Normal file
28
new-lamassu-admin/src/styling/icons/stripes.svg
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="100%"
|
||||||
|
height="36px">
|
||||||
|
<defs>
|
||||||
|
<pattern
|
||||||
|
id="pattern_68JiZ"
|
||||||
|
patternUnits="userSpaceOnUse"
|
||||||
|
width="5.5"
|
||||||
|
height="5.5"
|
||||||
|
patternTransform="rotate(45)">
|
||||||
|
<line
|
||||||
|
x1="0"
|
||||||
|
y="0"
|
||||||
|
x2="0"
|
||||||
|
y2="5.5"
|
||||||
|
stroke="#DBDFED"
|
||||||
|
stroke-width="3"
|
||||||
|
/>
|
||||||
|
</pattern>
|
||||||
|
</defs>{' '}
|
||||||
|
<rect
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
fill="url(#pattern_68JiZ)"
|
||||||
|
opacity="1"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 494 B |
|
|
@ -1,4 +1,7 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="64" version="1.1">
|
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="64">
|
||||||
<circle fill="#F7931A" cx="52" cy="32" r="32"/>
|
<circle cx="52" cy="32" r="32" fill="#F7931A" />
|
||||||
<path fill="#FFF" d="m66.103,27.444c0.637-4.258-2.605-6.547-7.038-8.074l1.438-5.768-3.511-0.875-1.4,5.616c-0.923-0.23-1.871-0.447-2.813-0.662l1.41-5.653-3.509-0.875-1.439,5.766c-0.764-0.174-1.514-0.346-2.242-0.527l0.004-0.018-4.842-1.209-0.934,3.75s2.605,0.597,2.55,0.634c1.422,0.355,1.679,1.296,1.636,2.042l-1.638,6.571c0.098,0.025,0.225,0.061,0.365,0.117-0.117-0.029-0.242-0.061-0.371-0.092l-2.296,9.205c-0.174,0.432-0.615,1.08-1.609,0.834,0.035,0.051-2.552-0.637-2.552-0.637l-1.743,4.019,4.569,1.139c0.85,0.213,1.683,0.436,2.503,0.646l-1.453,5.834,3.507,0.875,1.439-5.772c0.958,0.26,1.888,0.5,2.798,0.726l-1.434,5.745,3.511,0.875,1.453-5.823c5.987,1.133,10.489,0.676,12.384-4.739,1.527-4.36-0.076-6.875-3.226-8.515,2.294-0.529,4.022-2.038,4.483-5.155zm-8.022,11.249c-1.085,4.36-8.426,2.003-10.806,1.412l1.928-7.729c2.38,0.594,10.012,1.77,8.878,6.317zm1.086-11.312c-0.99,3.966-7.1,1.951-9.082,1.457l1.748-7.01c1.982,0.494,8.365,1.416,7.334,5.553z"/>
|
<path
|
||||||
</svg>
|
d="m66.1 27.4c0.6-4.3-2.6-6.5-7-8.1l1.4-5.8-3.5-0.9-1.4 5.6c-0.9-0.2-1.9-0.4-2.8-0.7l1.4-5.7-3.5-0.9-1.4 5.8c-0.8-0.2-1.5-0.3-2.2-0.5l0 0-4.8-1.2-0.9 3.8s2.6 0.6 2.6 0.6c1.4 0.4 1.7 1.3 1.6 2l-1.6 6.6c0.1 0 0.2 0.1 0.4 0.1-0.1 0-0.2-0.1-0.4-0.1l-2.3 9.2c-0.2 0.4-0.6 1.1-1.6 0.8 0 0.1-2.6-0.6-2.6-0.6l-1.7 4 4.6 1.1c0.9 0.2 1.7 0.4 2.5 0.6l-1.5 5.8 3.5 0.9 1.4-5.8c1 0.3 1.9 0.5 2.8 0.7l-1.4 5.7 3.5 0.9 1.5-5.8c6 1.1 10.5 0.7 12.4-4.7 1.5-4.4-0.1-6.9-3.2-8.5 2.3-0.5 4-2 4.5-5.2zm-8 11.2c-1.1 4.4-8.4 2-10.8 1.4l1.9-7.7c2.4 0.6 10 1.8 8.9 6.3zm1.1-11.3c-1 4-7.1 2-9.1 1.5l1.7-7c2 0.5 8.4 1.4 7.3 5.6z"
|
||||||
|
fill="#FFF"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 758 B |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue