fix: pre release screen fixes

This commit is contained in:
Taranto 2020-06-16 09:43:58 +01:00 committed by Josh Harvey
parent 1bcc87757b
commit 5dd8501a17
98 changed files with 1569 additions and 1149 deletions

View file

@ -25,16 +25,6 @@
"rejectAddressReuseActive" "rejectAddressReuseActive"
] ]
}, },
{
"code": "walletSettings",
"fields": [
"ticker",
"wallet",
"layer2",
"exchange",
"zeroConf"
]
},
{ {
"code": "notifications", "code": "notifications",
"fields": [ "fields": [

View file

@ -28,7 +28,7 @@ function post (machineTx, pi) {
.then(([{ config }, blacklistItems]) => { .then(([{ config }, blacklistItems]) => {
// TODO new-admin: addressReuse doesnt exist // TODO new-admin: addressReuse doesnt exist
// const rejectAddressReuseActive = configManager.unscoped(config).rejectAddressReuseActive // const rejectAddressReuseActive = configManager.unscoped(config).rejectAddressReuseActive
const rejectAddressReuseActive = true const rejectAddressReuseActive = false
if (_.some(it => it.created_by_operator === true)(blacklistItems)) { if (_.some(it => it.created_by_operator === true)(blacklistItems)) {
blacklisted = true blacklisted = true

View file

@ -32,6 +32,7 @@ function mapCoin (rates, deviceId, settings, cryptoCode) {
const cashInFee = showCommissions ? commissions.cashIn / 100 : null const cashInFee = showCommissions ? commissions.cashIn / 100 : null
const cashOutFee = showCommissions ? commissions.cashOut / 100 : null const cashOutFee = showCommissions ? commissions.cashOut / 100 : null
const cashInFixedFee = showCommissions ? commissions.fixedFee : null
const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', buildedRates) : null const cashInRate = showCommissions ? _.invoke('cashIn.toNumber', buildedRates) : null
const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', buildedRates) : null const cashOutRate = showCommissions ? _.invoke('cashOut.toNumber', buildedRates) : null
@ -77,6 +78,7 @@ function mapMachine (rates, settings, machineRow) {
// TODO new-admin: this is relaying info with backwards compatible triggers // TODO new-admin: this is relaying info with backwards compatible triggers
// need to get in touch with coinatmradar before updating this // need to get in touch with coinatmradar before updating this
// TODO all directions and max between them instead of min
const cashLimit = showLimitsAndVerification ? ( const cashLimit = showLimitsAndVerification ? (
!!compatTriggers.block !!compatTriggers.block
? compatTriggers.block ? compatTriggers.block

View file

@ -1,7 +1,7 @@
const _ = require('lodash/fp') const _ = require('lodash/fp')
function getBackwardsCompatibleTriggers (triggers) { function getBackwardsCompatibleTriggers (triggers) {
const filtered = _.filter(_.matches({ triggerType: 'volume', cashDirection: 'both' }))(triggers) const filtered = _.filter(_.matches({ triggerType: 'amount', cashDirection: 'both' }))(triggers)
const grouped = _.groupBy(_.prop('requirement'))(filtered) const grouped = _.groupBy(_.prop('requirement'))(filtered)
return _.mapValues(_.compose(_.get('threshold'), _.minBy('threshold')))(grouped) return _.mapValues(_.compose(_.get('threshold'), _.minBy('threshold')))(grouped)
} }

View file

@ -456,7 +456,7 @@ function getCustomerById (id) {
row_number() over (partition by c.id order by t.created desc) as rn, row_number() over (partition by c.id order by t.created desc) as rn,
count(0) over (partition by c.id) as total_txs, count(0) over (partition by c.id) as total_txs,
sum(t.fiat) over (partition by c.id) as total_spent sum(t.fiat) over (partition by c.id) as total_spent
from customers c inner join ( from customers c left outer join (
select 'cashIn' as tx_class, id, fiat, fiat_code, created, customer_id select 'cashIn' as tx_class, id, fiat, fiat_code, created, customer_id
from cash_in_txs where send_confirmed = true union from cash_in_txs where send_confirmed = true union
select 'cashOut' as tx_class, id, fiat, fiat_code, created, customer_id select 'cashOut' as tx_class, id, fiat, fiat_code, created, customer_id

View file

@ -11,7 +11,7 @@ const pgp = Pgp({
if (e.cn) logger.error('Database not reachable.') if (e.cn) logger.error('Database not reachable.')
if (e.query) { if (e.query) {
logger.error(e.query) logger.error(e.query)
logger.error(e.params) e.params && logger.error(e.params)
} }
logger.error(err) logger.error(err)
} }
@ -21,11 +21,12 @@ const db = pgp(psqlUrl)
eventBus.subscribe('log', args => { eventBus.subscribe('log', args => {
const { level, message, meta } = args const { level, message, meta } = args
const msgToSave = message ? message : _.get('message', meta)
const sql = `insert into server_logs const sql = `insert into server_logs
(id, device_id, message, log_level, meta) values ($1, $2, $3, $4, $5) returning *` (id, device_id, message, log_level, meta) values ($1, $2, $3, $4, $5) returning *`
db.one(sql, [uuid.v4(), '', message, level, meta]) db.one(sql, [uuid.v4(), '', msgToSave, level, meta])
.then(_.mapKeys(_.camelCase)) .then(_.mapKeys(_.camelCase))
}) })

View file

@ -1,6 +1,7 @@
const _ = require('lodash/fp') const _ = require('lodash/fp')
const axios = require('axios') const axios = require('axios')
const logger = require('./logger')
const db = require('./db') const db = require('./db')
const pairing = require('./pairing') const pairing = require('./pairing')
const configManager = require('./new-config-manager') const configManager = require('./new-config-manager')
@ -15,8 +16,10 @@ function getMachines () {
cashbox: r.cashbox, cashbox: r.cashbox,
cassette1: r.cassette1, cassette1: r.cassette1,
cassette2: r.cassette2, cassette2: r.cassette2,
pairedAt: new Date(r.created).valueOf(), version: r.version,
lastPing: new Date(r.last_online).valueOf(), model: r.model,
pairedAt: new Date(r.created),
lastPing: new Date(r.last_online),
name: r.name, name: r.name,
// TODO: we shall start using this JSON field at some point // TODO: we shall start using this JSON field at some point
// location: r.location, // location: r.location,
@ -36,19 +39,12 @@ function getMachineNames (config) {
const addName = r => { const addName = r => {
const cashOutConfig = configManager.getCashOut(r.deviceId, config) const cashOutConfig = configManager.getCashOut(r.deviceId, config)
const cashOut = cashOutConfig.active const cashOut = !!cashOutConfig.active
// TODO new-admin: these two fields were not ever working // TODO new-admin actually load status based on ping.
const machineModel = ''
const machineLocation = ''
// TODO: obtain next fields from somewhere
const printer = null
const pingTime = null
const statuses = [{label: 'Unknown detailed status', type: 'warning'}] const statuses = [{label: 'Unknown detailed status', type: 'warning'}]
const softwareVersion = ''
return _.assign(r, {cashOut, machineModel, machineLocation, printer, pingTime, statuses, softwareVersion}) return _.assign(r, {cashOut, statuses})
} }
return _.map(addName, machines) return _.map(addName, machines)

View file

@ -1,3 +1,4 @@
const _ = require('lodash/fp')
const { COINS, ALL_CRYPTOS } = require('./coins') const { COINS, ALL_CRYPTOS } = require('./coins')
const { BTC, BCH, DASH, ETH, LTC, ZEC } = COINS const { BTC, BCH, DASH, ETH, LTC, ZEC } = COINS

View file

@ -5,6 +5,7 @@ const configManager = require('../new-config-manager')
const wallet = require('../wallet') const wallet = require('../wallet')
const ticker = require('../ticker') const ticker = require('../ticker')
const coinUtils = require('../coin-utils') const coinUtils = require('../coin-utils')
const logger = require('../logger')
function allScopes (cryptoScopes, machineScopes) { function allScopes (cryptoScopes, machineScopes) {
const scopes = [] const scopes = []
@ -68,6 +69,9 @@ function getSingleCoinFunding (settings, fiatCode, cryptoCode) {
}) })
} }
// Promise.allSettled not running on current version of node
const reflect = p => p.then(value => ({value, status: "fulfilled" }), error => ({error: error.toString(), status: "rejected" }))
function getFunding () { function getFunding () {
return settingsLoader.loadLatest().then(settings => { return settingsLoader.loadLatest().then(settings => {
const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config) const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config)
@ -77,9 +81,10 @@ function getFunding () {
const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies) const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies)
const promises = cryptoDisplays.map(it => getSingleCoinFunding(settings, fiatCode, it.cryptoCode)) const promises = cryptoDisplays.map(it => getSingleCoinFunding(settings, fiatCode, it.cryptoCode))
return Promise.all(promises) return Promise.all(promises.map(reflect))
.then((response) => { .then((response) => {
return _.toArray(_.merge(response, cryptoDisplays)) const mapped = response.map(it => _.merge({ errorMsg: it.error }, it.value))
return _.toArray(_.merge(mapped, cryptoDisplays))
}) })
}) })
} }

View file

@ -19,7 +19,6 @@ const serverLogs = require('../server-logs')
const pairing = require('../pairing') const pairing = require('../pairing')
const { accounts: accountsConfig, coins, countries, currencies, languages } = require('../config') const { accounts: accountsConfig, coins, countries, currencies, languages } = require('../config')
// TODO why does server logs messages can be null?
const typeDefs = gql` const typeDefs = gql`
scalar JSON scalar JSON
scalar JSONObject scalar JSONObject
@ -54,6 +53,10 @@ const typeDefs = gql`
name: String! name: String!
deviceId: ID! deviceId: ID!
paired: Boolean! paired: Boolean!
lastPing: Date
pairedAt: Date
version: String
model: String
cashbox: Int cashbox: Int
cassette1: Int cassette1: Int
cassette2: Int cassette2: Int
@ -123,21 +126,22 @@ const typeDefs = gql`
type CoinFunds { type CoinFunds {
cryptoCode: String! cryptoCode: String!
fundingAddress: String! errorMsg: String
fundingAddressUrl: String! fundingAddress: String
confirmedBalance: String! fundingAddressUrl: String
pending: String! confirmedBalance: String
fiatConfirmedBalance: String! pending: String
fiatPending: String! fiatConfirmedBalance: String
fiatCode: String! fiatPending: String
display: String! fiatCode: String
unitScale: String! display: String
unitScale: String
} }
type ProcessStatus { type ProcessStatus {
name: String! name: String!
state: String! state: String!
uptime: Date! uptime: Int!
} }
type Transaction { type Transaction {
@ -156,6 +160,7 @@ const typeDefs = gql`
created: Date created: Date
send: Boolean send: Boolean
sendConfirmed: Boolean sendConfirmed: Boolean
dispense: Boolean
timedout: Boolean timedout: Boolean
sendTime: Date sendTime: Date
errorCode: String errorCode: String

View file

@ -2,6 +2,10 @@ const xmlrpc = require('xmlrpc')
const logger = require('../logger') const logger = require('../logger')
const { promisify } = require('util') const { promisify } = require('util')
// TODO new-admin: add the following to supervisor config
// [inet_http_server]
// port = 127.0.0.1:9001
function getAllProcessInfo () { function getAllProcessInfo () {
const convertStates = (state) => { const convertStates = (state) => {
// From http://supervisord.org/subprocess.html#process-states // From http://supervisord.org/subprocess.html#process-states
@ -45,7 +49,7 @@ function getAllProcessInfo () {
{ {
name: process.name, name: process.name,
state: convertStates(process.statename), state: convertStates(process.statename),
uptime: (process.statename === 'RUNNING') ? new Date(process.now) - new Date(process.start) : 0 uptime: (process.statename === 'RUNNING') ? process.now - process.start : 0
} }
)) ))
}) })

View file

@ -5,6 +5,7 @@ const FileAsync = require('lowdb/adapters/FileAsync')
const adapter = new FileAsync('db.json') const adapter = new FileAsync('db.json')
let db = null let db = null
// TODO new-admin save to actual db, like the old settings
low(adapter).then(it => { low(adapter).then(it => {
db = it db = it
}) })
@ -54,6 +55,7 @@ function loadLatest () {
}) })
} }
// TODO new-admin: grab correct version
function load (versionId) { function load (versionId) {
return new Promise((resolve) => { return new Promise((resolve) => {
if (!db) { if (!db) {

View file

@ -185,7 +185,6 @@ function plugins (settings, deviceId) {
const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config)
const minimumTx = BN(commissions.minimumTx) const minimumTx = BN(commissions.minimumTx)
const cashInFee = BN(commissions.fixedFee) const cashInFee = BN(commissions.fixedFee)
logger.info('FEE', cashInFee)
const cashInCommission = BN(commissions.cashIn) const cashInCommission = BN(commissions.cashIn)
const cashOutCommission = _.isNumber(commissions.cashOut) ? BN(commissions.cashOut) : null const cashOutCommission = _.isNumber(commissions.cashOut) ? BN(commissions.cashOut) : null
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
@ -201,7 +200,7 @@ function plugins (settings, deviceId) {
} }
} }
function pollQueries (serialNumber, deviceTime, deviceRec) { function pollQueries (serialNumber, deviceTime, deviceRec, machineVersion, machineModel) {
const localeConfig = configManager.getLocale(deviceId, settings.config) const localeConfig = configManager.getLocale(deviceId, settings.config)
const fiatCode = localeConfig.fiatCurrency const fiatCode = localeConfig.fiatCurrency
@ -210,7 +209,7 @@ function plugins (settings, deviceId) {
const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c)) const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c))
const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c)) const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c))
const testnetPromises = cryptoCodes.map(c => wallet.cryptoNetwork(settings, c)) const testnetPromises = cryptoCodes.map(c => wallet.cryptoNetwork(settings, c))
const pingPromise = recordPing(deviceTime) const pingPromise = recordPing(deviceTime, machineVersion, machineModel)
const currentConfigVersionPromise = fetchCurrentConfigVersion() const currentConfigVersionPromise = fetchCurrentConfigVersion()
const promises = [ const promises = [
@ -244,8 +243,10 @@ function plugins (settings, deviceId) {
return wallet.sendCoins(settings, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode) return wallet.sendCoins(settings, tx.toAddress, tx.cryptoAtoms, tx.cryptoCode)
} }
function recordPing (deviceTime) { function recordPing (deviceTime, version, model) {
const devices = { const devices = {
version,
model,
last_online: deviceTime last_online: deviceTime
} }

View file

@ -15,7 +15,6 @@ const logger = require('./logger')
const configManager = require('./new-config-manager') const configManager = require('./new-config-manager')
const complianceTriggers = require('./compliance-triggers') const complianceTriggers = require('./compliance-triggers')
const pairing = require('./pairing') const pairing = require('./pairing')
// TODO new-admin: remove old settings loader from here.
const newSettingsLoader = require('./new-settings-loader') const newSettingsLoader = require('./new-settings-loader')
const plugins = require('./plugins') const plugins = require('./plugins')
const helpers = require('./route-helpers') const helpers = require('./route-helpers')
@ -51,6 +50,8 @@ function checkHasLightning (settings) {
function poll (req, res, next) { function poll (req, res, next) {
const machineVersion = req.query.version const machineVersion = req.query.version
// TODO new-admin: pass this field from the machines
const machineModel = req.query.model
const deviceId = req.deviceId const deviceId = req.deviceId
const deviceTime = req.deviceTime const deviceTime = req.deviceTime
const serialNumber = req.query.sn const serialNumber = req.query.sn
@ -64,13 +65,12 @@ function poll (req, res, next) {
const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers) const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
const operatorInfo = configManager.getOperatorInfo(settings.config) const operatorInfo = configManager.getOperatorInfo(settings.config)
const terms = configManager.getTermsConditions(settings.config)
const cashOutConfig = configManager.getCashOut(deviceId, settings.config) const cashOutConfig = configManager.getCashOut(deviceId, settings.config)
const receipt = configManager.getReceipt(settings.config) const receipt = configManager.getReceipt(settings.config)
pids[deviceId] = { pid, ts: Date.now() } pids[deviceId] = { pid, ts: Date.now() }
return pi.pollQueries(serialNumber, deviceTime, req.query) return pi.pollQueries(serialNumber, deviceTime, req.query, machineVersion, machineModel)
.then(results => { .then(results => {
const cassettes = results.cassettes const cassettes = results.cassettes
@ -103,7 +103,7 @@ function poll (req, res, next) {
sanctionsVerificationThreshold: compatTriggers.sancations, sanctionsVerificationThreshold: compatTriggers.sancations,
frontCameraVerificationActive: !!compatTriggers.facephoto, frontCameraVerificationActive: !!compatTriggers.facephoto,
frontCameraVerificationThreshold: compatTriggers.facephoto, frontCameraVerificationThreshold: compatTriggers.facephoto,
receiptPrintingActive: receipt.active, receiptPrintingActive: receipt.active === "on",
cassettes, cassettes,
twoWayMode: cashOutConfig.active, twoWayMode: cashOutConfig.active,
zeroConfLimit: cashOutConfig.zeroConfLimit, zeroConfLimit: cashOutConfig.zeroConfLimit,
@ -389,7 +389,7 @@ const localApp = express()
app.use(compression({ threshold: 500 })) app.use(compression({ threshold: 500 }))
app.use(helmet({ noCache: true })) app.use(helmet({ noCache: true }))
app.use(bodyParser.json({ limit: '2mb' })) app.use(bodyParser.json({ limit: '2mb' }))
app.use(morgan('dev', { skip, stream: logger.stream })) app.use(morgan(':method :url :status :response-time ms - :res[content-length]', { stream: logger.stream }))
// These two have their own authorization // These two have their own authorization
app.post('/pair', populateDeviceId, pair) app.post('/pair', populateDeviceId, pair)

View file

@ -13,7 +13,6 @@ function _getRates (settings, fiatCode, cryptoCode) {
const config = settings.config const config = settings.config
const plugin = configManager.getWalletSettings(cryptoCode, config).ticker const plugin = configManager.getWalletSettings(cryptoCode, config).ticker
logger.info(plugin)
const account = settings.accounts[plugin] const account = settings.accounts[plugin]
const ticker = ph.load(ph.TICKER, plugin) const ticker = ph.load(ph.TICKER, plugin)

View file

@ -122,7 +122,7 @@ function mergeStatusMode (a, b) {
} }
function getWalletStatus (settings, tx) { function getWalletStatus (settings, tx) {
const fudgeFactorEnabled = configManager.unscoped(settings.config).fudgeFactorActive const fudgeFactorEnabled = configManager.getWalletSettings(tx.cryptoCode, settings.config).fudgeFactorActive
const fudgeFactor = fudgeFactorEnabled ? 100 : 0 const fudgeFactor = fudgeFactorEnabled ? 100 : 0
const walletStatusPromise = fetchWallet(settings, tx.cryptoCode) const walletStatusPromise = fetchWallet(settings, tx.cryptoCode)

View file

@ -0,0 +1,14 @@
const db = require('./db')
exports.up = function (next) {
var sql = [
'alter table devices add column version text',
'alter table devices add column model text'
]
db.multi(sql, next)
}
exports.down = function (next) {
next()
}

View file

@ -4867,123 +4867,124 @@
} }
} }
}, },
"apollo-boost": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/apollo-boost/-/apollo-boost-0.4.7.tgz",
"integrity": "sha512-jfc3aqO0vpCV+W662EOG5gq4AH94yIsvSgAUuDvS3o/Z+8Joqn4zGC9CgLCDHusK30mFgtsEgwEe0pZoedohsQ==",
"requires": {
"apollo-cache": "^1.3.4",
"apollo-cache-inmemory": "^1.6.5",
"apollo-client": "^2.6.7",
"apollo-link": "^1.0.6",
"apollo-link-error": "^1.0.3",
"apollo-link-http": "^1.3.1",
"graphql-tag": "^2.4.2",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"apollo-cache": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.4.tgz",
"integrity": "sha512-7X5aGbqaOWYG+SSkCzJNHTz2ZKDcyRwtmvW4mGVLRqdQs+HxfXS4dUS2CcwrAj449se6tZ6NLUMnjko4KMt3KA==",
"requires": {
"apollo-utilities": "^1.3.3",
"tslib": "^1.10.0"
},
"dependencies": {
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
}
}
},
"apollo-cache-inmemory": { "apollo-cache-inmemory": {
"version": "1.6.5", "version": "1.6.6",
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz", "resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.6.tgz",
"integrity": "sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA==", "integrity": "sha512-L8pToTW/+Xru2FFAhkZ1OA9q4V4nuvfoPecBM34DecAugUZEBhI2Hmpgnzq2hTKZ60LAMrlqiASm0aqAY6F8/A==",
"requires": { "requires": {
"apollo-cache": "^1.3.4", "apollo-cache": "^1.3.5",
"apollo-utilities": "^1.3.3", "apollo-utilities": "^1.3.4",
"optimism": "^0.10.0", "optimism": "^0.10.0",
"ts-invariant": "^0.4.0", "ts-invariant": "^0.4.0",
"tslib": "^1.10.0" "tslib": "^1.10.0"
}, },
"dependencies": { "dependencies": {
"apollo-cache": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.5.tgz",
"integrity": "sha512-1XoDy8kJnyWY/i/+gLTEbYLnoiVtS8y7ikBr/IfmML4Qb+CM7dEEbIUOjnY716WqmZ/UpXIxTfJsY7rMcqiCXA==",
"requires": {
"apollo-utilities": "^1.3.4",
"tslib": "^1.10.0"
}
},
"apollo-utilities": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz",
"integrity": "sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==",
"requires": {
"@wry/equality": "^0.1.2",
"fast-json-stable-stringify": "^2.0.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
}
},
"tslib": { "tslib": {
"version": "1.10.0", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
} }
} }
}, },
"apollo-client": { "apollo-client": {
"version": "2.6.8", "version": "2.6.10",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.8.tgz", "resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.10.tgz",
"integrity": "sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw==", "integrity": "sha512-jiPlMTN6/5CjZpJOkGeUV0mb4zxx33uXWdj/xQCfAMkuNAC3HN7CvYDyMHHEzmcQ5GV12LszWoQ/VlxET24CtA==",
"requires": { "requires": {
"@types/zen-observable": "^0.8.0", "@types/zen-observable": "^0.8.0",
"apollo-cache": "1.3.4", "apollo-cache": "1.3.5",
"apollo-link": "^1.0.0", "apollo-link": "^1.0.0",
"apollo-utilities": "1.3.3", "apollo-utilities": "1.3.4",
"symbol-observable": "^1.0.2", "symbol-observable": "^1.0.2",
"ts-invariant": "^0.4.0", "ts-invariant": "^0.4.0",
"tslib": "^1.10.0", "tslib": "^1.10.0",
"zen-observable": "^0.8.0" "zen-observable": "^0.8.0"
}, },
"dependencies": { "dependencies": {
"apollo-cache": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.3.5.tgz",
"integrity": "sha512-1XoDy8kJnyWY/i/+gLTEbYLnoiVtS8y7ikBr/IfmML4Qb+CM7dEEbIUOjnY716WqmZ/UpXIxTfJsY7rMcqiCXA==",
"requires": {
"apollo-utilities": "^1.3.4",
"tslib": "^1.10.0"
}
},
"apollo-utilities": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.3.4.tgz",
"integrity": "sha512-pk2hiWrCXMAy2fRPwEyhvka+mqwzeP60Jr1tRYi5xru+3ko94HI9o6lK0CT33/w4RDlxWchmdhDCrvdr+pHCig==",
"requires": {
"@wry/equality": "^0.1.2",
"fast-json-stable-stringify": "^2.0.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
}
},
"tslib": { "tslib": {
"version": "1.10.0", "version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
} }
} }
}, },
"apollo-link": { "apollo-link": {
"version": "1.2.13", "version": "1.2.14",
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.13.tgz", "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz",
"integrity": "sha512-+iBMcYeevMm1JpYgwDEIDt/y0BB7VWyvlm/7x+TIPNLHCTCMgcEgDuW5kH86iQZWo0I7mNwQiTOz+/3ShPFmBw==", "integrity": "sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==",
"requires": { "requires": {
"apollo-utilities": "^1.3.0", "apollo-utilities": "^1.3.0",
"ts-invariant": "^0.4.0", "ts-invariant": "^0.4.0",
"tslib": "^1.9.3", "tslib": "^1.9.3",
"zen-observable-ts": "^0.8.20" "zen-observable-ts": "^0.8.21"
} }
}, },
"apollo-link-error": { "apollo-link-error": {
"version": "1.1.12", "version": "1.1.13",
"resolved": "https://registry.npmjs.org/apollo-link-error/-/apollo-link-error-1.1.12.tgz", "resolved": "https://registry.npmjs.org/apollo-link-error/-/apollo-link-error-1.1.13.tgz",
"integrity": "sha512-psNmHyuy3valGikt/XHJfe0pKJnRX19tLLs6P6EHRxg+6q6JMXNVLYPaQBkL0FkwdTCB0cbFJAGRYCBviG8TDA==", "integrity": "sha512-jAZOOahJU6bwSqb2ZyskEK1XdgUY9nkmeclCrW7Gddh1uasHVqmoYc4CKdb0/H0Y1J9lvaXKle2Wsw/Zx1AyUg==",
"requires": { "requires": {
"apollo-link": "^1.2.13", "apollo-link": "^1.2.14",
"apollo-link-http-common": "^0.2.15", "apollo-link-http-common": "^0.2.16",
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
}, },
"apollo-link-http": { "apollo-link-http": {
"version": "1.5.16", "version": "1.5.17",
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.16.tgz", "resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.17.tgz",
"integrity": "sha512-IA3xA/OcrOzINRZEECI6IdhRp/Twom5X5L9jMehfzEo2AXdeRwAMlH5LuvTZHgKD8V1MBnXdM6YXawXkTDSmJw==", "integrity": "sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg==",
"requires": { "requires": {
"apollo-link": "^1.2.13", "apollo-link": "^1.2.14",
"apollo-link-http-common": "^0.2.15", "apollo-link-http-common": "^0.2.16",
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
}, },
"apollo-link-http-common": { "apollo-link-http-common": {
"version": "0.2.15", "version": "0.2.16",
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.15.tgz", "resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz",
"integrity": "sha512-+Heey4S2IPsPyTf8Ag3PugUupASJMW894iVps6hXbvwtg1aHSNMXUYO5VG7iRHkPzqpuzT4HMBanCTXPjtGzxg==", "integrity": "sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg==",
"requires": { "requires": {
"apollo-link": "^1.2.13", "apollo-link": "^1.2.14",
"ts-invariant": "^0.4.0", "ts-invariant": "^0.4.0",
"tslib": "^1.9.3" "tslib": "^1.9.3"
} }
@ -11762,9 +11763,9 @@
} }
}, },
"graphql-tag": { "graphql-tag": {
"version": "2.10.1", "version": "2.10.3",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.1.tgz", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.3.tgz",
"integrity": "sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg==" "integrity": "sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA=="
}, },
"growly": { "growly": {
"version": "1.3.0", "version": "1.3.0",
@ -26657,9 +26658,9 @@
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
}, },
"zen-observable-ts": { "zen-observable-ts": {
"version": "0.8.20", "version": "0.8.21",
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.20.tgz", "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz",
"integrity": "sha512-2rkjiPALhOtRaDX6pWyNqK1fnP5KkJJybYebopNSn6wDG1lxBoFs2+nwwXKoA6glHIrtwrfBBy6da0stkKtTAA==", "integrity": "sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==",
"requires": { "requires": {
"tslib": "^1.9.3", "tslib": "^1.9.3",
"zen-observable": "^0.8.0" "zen-observable": "^0.8.0"

View file

@ -8,7 +8,11 @@
"@material-ui/icons": "4.9.1", "@material-ui/icons": "4.9.1",
"@material-ui/lab": "^4.0.0-alpha.47", "@material-ui/lab": "^4.0.0-alpha.47",
"@use-hooks/axios": "1.3.0", "@use-hooks/axios": "1.3.0",
"apollo-boost": "^0.4.7", "apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.10",
"apollo-link": "^1.2.14",
"apollo-link-error": "^1.1.13",
"apollo-link-http": "^1.5.17",
"axios": "0.19.0", "axios": "0.19.0",
"bignumber.js": "9.0.0", "bignumber.js": "9.0.0",
"classnames": "2.2.6", "classnames": "2.2.6",
@ -17,6 +21,7 @@
"formik": "2.1.4", "formik": "2.1.4",
"fuse.js": "^3.4.6", "fuse.js": "^3.4.6",
"graphql": "^14.5.8", "graphql": "^14.5.8",
"graphql-tag": "^2.10.3",
"jss-plugin-extend": "^10.0.0", "jss-plugin-extend": "^10.0.0",
"libphonenumber-js": "^1.7.50", "libphonenumber-js": "^1.7.50",
"moment": "2.24.0", "moment": "2.24.0",

View file

@ -6,42 +6,20 @@ import {
MuiThemeProvider, MuiThemeProvider,
makeStyles makeStyles
} from '@material-ui/core/styles' } from '@material-ui/core/styles'
import ApolloClient from 'apollo-boost'
import { setAutoFreeze } from 'immer' import { setAutoFreeze } from 'immer'
import { create } from 'jss' import { create } from 'jss'
import extendJss from 'jss-plugin-extend' 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 client from 'src/utils/apollo'
import Header from './components/layout/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'
import { backgroundColor, mainWidth } from './styling/variables' import { backgroundColor, mainWidth } from './styling/variables'
const defaultOptions = {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'ignore'
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all'
},
mutate: {
errorPolicy: 'all'
}
}
const client = new ApolloClient({
credentials: 'include',
defaultOptions,
uri:
process.env.NODE_ENV === 'development'
? 'https://localhost:8070/graphql/'
: '/graphql'
})
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
const whyDidYouRender = require('@welldone-software/why-did-you-render') const whyDidYouRender = require('@welldone-software/why-did-you-render')
whyDidYouRender(React) whyDidYouRender(React)

View file

@ -3,41 +3,53 @@ import {
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogContentText, DialogContentText,
DialogTitle as MuiDialogTitle,
IconButton,
makeStyles makeStyles
} from '@material-ui/core' } from '@material-ui/core'
import React, { useState, memo } from 'react' import React, { useEffect, useState, memo } from 'react'
import { Button } from '../components/buttons' import { Button, IconButton } from 'src/components/buttons'
import { ReactComponent as CloseIcon } from '../styling/icons/action/close/zodiac.svg' import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
import { spacer } from '../styling/variables'
import { TextInput } from './inputs' import { TextInput } from './inputs'
import { H4, P } from './typography' import { H4, P } from './typography'
const useStyles = makeStyles({ const useStyles = makeStyles({
label: {
fontSize: 16
},
spacing: {
padding: 32
},
wrapper: {
display: 'flex'
},
title: {
margin: [[20, 0, 24, 16]]
},
closeButton: { closeButton: {
position: 'absolute', padding: 0,
right: spacer, margin: [[12, 12, 'auto', 'auto']]
top: spacer // position: 'absolute',
// right: spacer,
// top: spacer
} }
}) })
export const DialogTitle = ({ children, onClose }) => { export const DialogTitle = ({ children, onClose }) => {
const classes = useStyles() const classes = useStyles()
return ( return (
<MuiDialogTitle> <div className={classes.wrapper}>
{children} {children}
{onClose && ( {onClose && (
<IconButton <IconButton
size={16}
aria-label="close" aria-label="close"
className={classes.closeButton} className={classes.closeButton}
onClick={onClose}> onClick={onClose}>
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
)} )}
</MuiDialogTitle> </div>
) )
} }
@ -49,9 +61,12 @@ export const ConfirmDialog = memo(
toBeConfirmed, toBeConfirmed,
onConfirmed, onConfirmed,
onDissmised, onDissmised,
className,
...props ...props
}) => { }) => {
const classes = useStyles()
const [value, setValue] = useState('') const [value, setValue] = useState('')
useEffect(() => setValue(''), [open])
const handleChange = event => { const handleChange = event => {
setValue(event.target.value) setValue(event.target.value)
} }
@ -59,14 +74,14 @@ export const ConfirmDialog = memo(
return ( return (
<Dialog open={open} aria-labelledby="form-dialog-title" {...props}> <Dialog open={open} aria-labelledby="form-dialog-title" {...props}>
<DialogTitle id="customized-dialog-title" onClose={onDissmised}> <DialogTitle id="customized-dialog-title" onClose={onDissmised}>
<H4>{title}</H4> <H4 className={classes.title}>{title}</H4>
{subtitle && ( {subtitle && (
<DialogContentText> <DialogContentText>
<P>{subtitle}</P> <P>{subtitle}</P>
</DialogContentText> </DialogContentText>
)} )}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent className={className}>
<TextInput <TextInput
label={`Write '${toBeConfirmed}' to confirm`} label={`Write '${toBeConfirmed}' to confirm`}
name="confirm-input" name="confirm-input"
@ -78,11 +93,11 @@ export const ConfirmDialog = memo(
value={value} value={value}
touched={{}} touched={{}}
error={toBeConfirmed !== value} error={toBeConfirmed !== value}
InputLabelProps={{ shrink: true }} InputLabelProps={{ shrink: true, className: classes.label }}
onChange={handleChange} onChange={handleChange}
/> />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions classes={{ spacing: classes.spacing }}>
<Button <Button
color="green" color="green"
disabled={toBeConfirmed !== value} disabled={toBeConfirmed !== value}

View file

@ -0,0 +1,55 @@
import { makeStyles, ClickAwayListener } from '@material-ui/core'
import React, { useState, memo } from 'react'
import Popper from 'src/components/Popper'
import { P } from 'src/components/typography'
import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg'
const useStyles = makeStyles({
transparentButton: {
border: 'none',
backgroundColor: 'transparent',
marginTop: 4,
cursor: 'pointer'
},
popoverContent: ({ width }) => ({
width,
padding: [[10, 15]]
})
})
const HelpTooltip = memo(({ children, width }) => {
const classes = useStyles({ width })
const [helpPopperAnchorEl, setHelpPopperAnchorEl] = useState(null)
const handleOpenHelpPopper = event => {
setHelpPopperAnchorEl(helpPopperAnchorEl ? null : event.currentTarget)
}
const handleCloseHelpPopper = () => {
setHelpPopperAnchorEl(null)
}
const helpPopperOpen = Boolean(helpPopperAnchorEl)
return (
<ClickAwayListener onClickAway={handleCloseHelpPopper}>
<button
className={classes.transparentButton}
onClick={handleOpenHelpPopper}>
<HelpIcon />
<Popper
open={helpPopperOpen}
anchorEl={helpPopperAnchorEl}
placement="bottom"
onClose={handleCloseHelpPopper}>
<div className={classes.popoverContent}>
<P>{children}</P>
</div>
</Popper>
</button>
</ClickAwayListener>
)
})
export default HelpTooltip

View file

@ -5,11 +5,13 @@ import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState, useCallback } from 'react' import React, { useState, useCallback } from 'react'
import { FeatureButton, Link } from 'src/components/buttons'
import { ReactComponent as Arrow } from 'src/styling/icons/arrow/download_logs.svg' import { ReactComponent as Arrow } from 'src/styling/icons/arrow/download_logs.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 { primaryColor, offColor, zircon } from 'src/styling/variables' import { primaryColor, offColor, zircon } from 'src/styling/variables'
import Popper from './Popper' import Popper from './Popper'
import { Link } from './buttons'
import DateRangePicker from './date-range-picker/DateRangePicker' import DateRangePicker from './date-range-picker/DateRangePicker'
import { RadioGroup } from './inputs' import { RadioGroup } from './inputs'
import typographyStyles from './typography/styles' import typographyStyles from './typography/styles'
@ -123,30 +125,26 @@ const styles = {
} }
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const ALL = 'all'
const RANGE = 'range'
const LogsDownloaderPopover = ({ const LogsDownloaderPopover = ({ name, getTimestamp, logs, title }) => {
id, const [selectedRadio, setSelectedRadio] = useState(ALL)
name, const [range, setRange] = useState({ from: null, to: null })
open, const [anchorEl, setAnchorEl] = useState(null)
anchorEl,
getTimestamp,
logs,
title
}) => {
const radioButtonAll = 'all'
const radioButtonRange = 'range'
const [selectedRadio, setSelectedRadio] = useState(radioButtonAll)
const [range, setRange] = useState(null)
const classes = useStyles() const classes = useStyles()
const dateRangePickerClasses = { const dateRangePickerClasses = {
[classes.dateRangePickerShowing]: selectedRadio === radioButtonRange, [classes.dateRangePickerShowing]: selectedRadio === RANGE,
[classes.dateRangePickerHidden]: selectedRadio === radioButtonAll [classes.dateRangePickerHidden]: selectedRadio === ALL
} }
const handleRadioButtons = R.o(setSelectedRadio, R.path(['target', 'value'])) const handleRadioButtons = evt => {
const selectedRadio = R.path(['target', 'value'])(evt)
setSelectedRadio(selectedRadio)
if (selectedRadio === ALL) setRange({ from: null, to: null })
}
const handleRangeChange = useCallback( const handleRangeChange = useCallback(
(from, to) => { (from, to) => {
@ -164,7 +162,7 @@ const LogsDownloaderPopover = ({
return moment(date).format('YYYY-MM-DD_HH-mm') return moment(date).format('YYYY-MM-DD_HH-mm')
} }
if (selectedRadio === radioButtonAll) { if (selectedRadio === ALL) {
const text = logs.map(it => JSON.stringify(it)).join('\n') const text = logs.map(it => JSON.stringify(it)).join('\n')
const blob = new window.Blob([text], { const blob = new window.Blob([text], {
type: 'text/plain;charset=utf-8' type: 'text/plain;charset=utf-8'
@ -173,7 +171,7 @@ const LogsDownloaderPopover = ({
return return
} }
if (selectedRadio === radioButtonRange) { if (selectedRadio === RANGE) {
const text = logs const text = logs
.filter(log => .filter(log =>
moment(getTimestamp(log)).isBetween(range.from, range.to, 'day', '[]') moment(getTimestamp(log)).isBetween(range.from, range.to, 'day', '[]')
@ -190,51 +188,67 @@ const LogsDownloaderPopover = ({
} }
} }
const handleOpenRangePicker = event => {
setAnchorEl(anchorEl ? null : event.currentTarget)
}
const radioButtonOptions = [ const radioButtonOptions = [
{ display: 'All logs', code: radioButtonAll }, { display: 'All logs', code: ALL },
{ display: 'Date range', code: radioButtonRange } { display: 'Date range', code: RANGE }
] ]
const open = Boolean(anchorEl)
const id = open ? 'date-range-popover' : undefined
return ( return (
<Popper id={id} open={open} anchorEl={anchorEl} placement="bottom"> <>
<div className={classes.popoverContent}> <FeatureButton
<div className={classes.popoverHeader}>{title}</div> Icon={Download}
<div className={classes.radioButtonsContainer}> InverseIcon={DownloadInverseIcon}
<RadioGroup aria-describedby={id}
name="logs-select" variant="contained"
value={selectedRadio} onClick={handleOpenRangePicker}
options={radioButtonOptions} />
ariaLabel="logs-select" <Popper id={id} open={open} anchorEl={anchorEl} placement="bottom">
onChange={handleRadioButtons} <div className={classes.popoverContent}>
className={classes.radioButtons} <div className={classes.popoverHeader}>{title}</div>
/> <div className={classes.radioButtonsContainer}>
</div> <RadioGroup
{selectedRadio === radioButtonRange && ( name="logs-select"
<div className={classnames(dateRangePickerClasses)}> value={selectedRadio}
<div className={classes.dateContainerWrapper}> options={radioButtonOptions}
{range && ( ariaLabel="logs-select"
<> onChange={handleRadioButtons}
<DateContainer date={range.from}>From</DateContainer> className={classes.radioButtons}
<div className={classes.arrowContainer}>
<Arrow className={classes.arrow} />
</div>
<DateContainer date={range.to}>To</DateContainer>
</>
)}
</div>
<DateRangePicker
maxDate={moment()}
onRangeChange={handleRangeChange}
/> />
</div> </div>
)} {selectedRadio === RANGE && (
<div className={classes.download}> <div className={classnames(dateRangePickerClasses)}>
<Link color="primary" onClick={() => downloadLogs(range, logs)}> <div className={classes.dateContainerWrapper}>
Download {range && (
</Link> <>
<DateContainer date={range.from}>From</DateContainer>
<div className={classes.arrowContainer}>
<Arrow className={classes.arrow} />
</div>
<DateContainer date={range.to}>To</DateContainer>
</>
)}
</div>
<DateRangePicker
maxDate={moment()}
onRangeChange={handleRangeChange}
/>
</div>
)}
<div className={classes.download}>
<Link color="primary" onClick={() => downloadLogs(range, logs)}>
Download
</Link>
</div>
</div> </div>
</div> </Popper>
</Popper> </>
) )
} }

View file

@ -3,7 +3,7 @@ import classnames from 'classnames'
import React from 'react' import React from 'react'
import { IconButton } from 'src/components/buttons' import { IconButton } from 'src/components/buttons'
import { H1, H2 } from 'src/components/typography' import { H1, H4 } 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 = {
@ -18,29 +18,29 @@ const styles = {
height, height,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
minHeight: 400, minHeight: height ?? 400,
maxHeight: '90vh', maxHeight: '90vh',
overflowY: 'auto', overflowY: 'auto',
borderRadius: 8, borderRadius: 8,
outline: 0 outline: 0
}), }),
content: { content: ({ small }) => ({
width: '100%', width: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
flex: 1, flex: 1,
padding: [[0, 32]] padding: small ? [[0, 16]] : [[0, 32]]
}, }),
button: { button: ({ small }) => ({
padding: 0, padding: 0,
margin: [[20, 20, 'auto', 'auto']] margin: small ? [[12, 12, 'auto', 'auto']] : [[16, 16, 'auto', 'auto']]
}, }),
header: { header: {
display: 'flex' display: 'flex'
}, },
title: { title: ({ small }) => ({
margin: [[28, 0, 8, 32]] margin: small ? [[20, 0, 8, 16]] : [[28, 0, 8, 32]]
} })
} }
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
@ -49,7 +49,7 @@ const Modal = ({
width, width,
height, height,
title, title,
titleSmall, small,
infoPanel, infoPanel,
handleClose, handleClose,
children, children,
@ -58,9 +58,9 @@ const Modal = ({
closeOnBackdropClick, closeOnBackdropClick,
...props ...props
}) => { }) => {
const classes = useStyles({ width, height }) const classes = useStyles({ width, height, small })
const TitleCase = titleSmall ? H2 : H1 const TitleCase = small ? H4 : H1
const closeSize = titleSmall ? 16 : 20 const closeSize = small ? 16 : 20
const innerClose = (evt, reason) => { const innerClose = (evt, reason) => {
if (!closeOnBackdropClick && reason === 'backdropClick') return if (!closeOnBackdropClick && reason === 'backdropClick') return

View file

@ -5,7 +5,13 @@ import React, { useState } from 'react'
import { white } from 'src/styling/variables' import { white } from 'src/styling/variables'
const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => { const Popover = ({
children,
bgColor = white,
arrowSize = 6,
className,
...props
}) => {
const [arrowRef, setArrowRef] = useState(null) const [arrowRef, setArrowRef] = useState(null)
const styles = { const styles = {
@ -27,7 +33,24 @@ const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => {
borderLeft: [['2em', 'solid', 'transparent']], borderLeft: [['2em', 'solid', 'transparent']],
borderRight: [['2em', 'solid', 'transparent']], borderRight: [['2em', 'solid', 'transparent']],
borderBottom: [['2em', 'solid', bgColor]], borderBottom: [['2em', 'solid', bgColor]],
marginTop: '-1.9em' marginTop: '-1.9em',
'&:after': {
zIndex: -10,
content: '""',
position: 'absolute',
width: arrowSize * 3,
height: arrowSize * 3,
marginLeft: 0,
bottom: 0,
top: 'calc(50% - 0px)',
left: 0,
border: '5px solid #fff',
borderColor: 'transparent transparent #fff #fff',
transformOrigin: '0 0',
transform: 'rotate(45deg)',
boxShadow:
'0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)'
}
}, },
arrowTop: { arrowTop: {
bottom: 0, bottom: 0,
@ -36,7 +59,24 @@ const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => {
borderLeft: [['2em', 'solid', 'transparent']], borderLeft: [['2em', 'solid', 'transparent']],
borderRight: [['2em', 'solid', 'transparent']], borderRight: [['2em', 'solid', 'transparent']],
borderTop: [['2em', 'solid', bgColor]], borderTop: [['2em', 'solid', bgColor]],
marginBottom: '-1.9em' marginBottom: '-1.9em',
'&:after': {
zIndex: -10,
content: '""',
position: 'absolute',
width: arrowSize * 3,
height: arrowSize * 3,
marginLeft: 0,
bottom: 0,
top: -(arrowSize * 4 + 2),
left: 0,
border: '5px solid #fff',
borderColor: 'transparent transparent #fff #fff',
transformOrigin: '0 0',
transform: 'rotate(45deg)',
boxShadow:
'0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12)'
}
}, },
arrowRight: { arrowRight: {
left: 0, left: 0,
@ -81,6 +121,10 @@ const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => {
enabled: true, enabled: true,
boundariesElement: 'scrollParent' boundariesElement: 'scrollParent'
}, },
offset: {
enabled: true,
offset: '0, 10'
},
arrow: { arrow: {
enabled: true, enabled: true,
element: arrowRef element: arrowRef
@ -94,7 +138,7 @@ const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => {
modifiers={modifiers} modifiers={modifiers}
className={classes.popover} className={classes.popover}
{...props}> {...props}>
<Paper className={classes.root}> <Paper className={classnames(classes.root, className)}>
<span className={classnames(arrowClasses)} ref={setArrowRef} /> <span className={classnames(arrowClasses)} ref={setArrowRef} />
{children} {children}
</Paper> </Paper>

View file

@ -1,6 +1,6 @@
import React from 'react'
import Chip from '@material-ui/core/Chip' import Chip from '@material-ui/core/Chip'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import React from 'react'
import { import {
tomato, tomato,
@ -40,15 +40,21 @@ const useStyles = makeStyles({
fontSize: smallestFontSize, fontSize: smallestFontSize,
fontWeight: inputFontWeight, fontWeight: inputFontWeight,
fontFamily: inputFontFamily, fontFamily: inputFontFamily,
paddingRight: spacer / 2, padding: [[spacer / 2, spacer]],
paddingLeft: spacer / 2,
color: ({ type }) => colors[type] color: ({ type }) => colors[type]
} }
}) })
const Status = ({ status }) => { const Status = ({ status, className }) => {
const classes = useStyles({ type: status.type }) const classes = useStyles({ type: status.type })
return <Chip type={status.type} label={status.label} classes={classes} /> return (
<Chip
type={status.type}
label={status.label}
className={className ?? null}
classes={classes}
/>
)
} }
const MainStatus = ({ statuses }) => { const MainStatus = ({ statuses }) => {
@ -59,7 +65,7 @@ const MainStatus = ({ statuses }) => {
const plus = { label: `+${statuses.length - 1}`, type: mainStatus.type } const plus = { label: `+${statuses.length - 1}`, type: mainStatus.type }
return ( return (
<div> <div style={{ marginLeft: -3 }}>
<Status status={mainStatus} /> <Status status={mainStatus} />
{statuses.length > 1 && <Status status={plus} />} {statuses.length > 1 && <Status status={plus} />}
</div> </div>

View file

@ -7,7 +7,7 @@ import { H1 } from './typography'
const useStyles = makeStyles({ const useStyles = makeStyles({
title: { title: {
marginTop: spacer * 3.5, marginTop: spacer * 3,
marginBottom: spacer * 3 marginBottom: spacer * 3
} }
}) })

View file

@ -1,3 +1,4 @@
import typographyStyles from 'src/components/typography/styles'
import { import {
white, white,
fontColor, fontColor,
@ -6,7 +7,6 @@ import {
offColor, offColor,
offDarkColor offDarkColor
} from 'src/styling/variables' } from 'src/styling/variables'
import typographyStyles from 'src/components/typography/styles'
const { p } = typographyStyles const { p } = typographyStyles
@ -27,7 +27,7 @@ export default {
extend: p, extend: p,
cursor: 'pointer', cursor: 'pointer',
border: 'none', border: 'none',
height: 24, height: 28,
outline: 0, outline: 0,
borderRadius: 6, borderRadius: 6,
padding: '0 8px', padding: '0 8px',

View file

@ -11,13 +11,19 @@ const styles = {
extend: baseButton, extend: baseButton,
width: baseButton.height, width: baseButton.height,
borderRadius: baseButton.height / 2, borderRadius: baseButton.height / 2,
display: 'flex' display: 'flex',
padding: 0
}, },
primary, primary,
buttonIcon: { buttonIcon: {
margin: 'auto', margin: 'auto',
'& svg': { '& svg': {
overflow: 'visible' width: 16,
height: 16,
overflow: 'visible',
'& g': {
strokeWidth: 1.8
}
} }
}, },
buttonIconActive: {} // required to extend primary buttonIconActive: {} // required to extend primary
@ -25,29 +31,35 @@ const styles = {
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const FeatureButton = memo(({ className, Icon, InverseIcon, ...props }) => { const FeatureButton = memo(
const classes = useStyles() ({ className, Icon, InverseIcon, children, ...props }) => {
const classes = useStyles()
const classNames = { const classNames = {
[classes.featureButton]: true, [classes.featureButton]: true,
[classes.primary]: true [classes.primary]: true
}
return (
<button className={classnames(classNames, className)} {...props}>
{Icon && (
<div className={classes.buttonIcon}>
<Icon />
</div>
)}
{InverseIcon && (
<div
className={classnames(
classes.buttonIcon,
classes.buttonIconActive
)}>
<InverseIcon />
</div>
)}
{children}
</button>
)
} }
)
return (
<button className={classnames(classNames, className)} {...props}>
{Icon && (
<div className={classes.buttonIcon}>
<Icon />
</div>
)}
{InverseIcon && (
<div
className={classnames(classes.buttonIcon, classes.buttonIconActive)}>
<InverseIcon />
</div>
)}
</button>
)
})
export default FeatureButton export default FeatureButton

View file

@ -2,13 +2,13 @@ import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames' import classnames from 'classnames'
import React, { useState, memo } from 'react' import React, { useState, memo } from 'react'
import Popover from 'src/components/Popper'
import typographyStyles from 'src/components/typography/styles'
import { import {
subheaderColor, subheaderColor,
subheaderDarkColor, subheaderDarkColor,
offColor offColor
} from 'src/styling/variables' } from 'src/styling/variables'
import Popover from 'src/components/Popper'
import typographyStyles from 'src/components/typography/styles'
const { info2 } = typographyStyles const { info2 } = typographyStyles
@ -70,6 +70,7 @@ const IDButton = memo(
InverseIcon, InverseIcon,
popoverWidth = 152, popoverWidth = 152,
children, children,
popoverClassname,
...props ...props
}) => { }) => {
const [anchorEl, setAnchorEl] = useState(null) const [anchorEl, setAnchorEl] = useState(null)
@ -117,6 +118,7 @@ const IDButton = memo(
)} )}
</button> </button>
<Popover <Popover
className={popoverClassname}
id={id} id={id}
open={open} open={open}
anchorEl={anchorEl} anchorEl={anchorEl}

View file

@ -4,7 +4,7 @@ import React, { memo } from 'react'
import baseButtonStyles from './BaseButton.styles' import baseButtonStyles from './BaseButton.styles'
const { baseButton } = baseButtonStyles const { baseButton, primary } = baseButtonStyles
const styles = { const styles = {
button: { button: {
@ -12,19 +12,48 @@ const styles = {
borderRadius: baseButton.height / 2, borderRadius: baseButton.height / 2,
outline: 0, outline: 0,
padding: '0 20px' padding: '0 20px'
} },
primary,
buttonIcon: {
marginTop: 4,
marginRight: 4,
'& svg': {
width: 20,
height: 20,
overflow: 'visible'
}
},
buttonIconActive: {} // required to extend primary
} }
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const SimpleButton = memo(({ className, children, color, size, ...props }) => { const SimpleButton = memo(
const classes = useStyles() ({ className, Icon, InverseIcon, children, color, size, ...props }) => {
const classes = useStyles()
return ( return (
<button className={classnames(classes.button, className)} {...props}> <button
{children} className={classnames(classes.primary, classes.button, className)}
</button> {...props}>
) {Icon && (
}) <div className={classes.buttonIcon}>
<Icon />
</div>
)}
{InverseIcon && (
<div
className={classnames(
classes.buttonIcon,
classes.buttonIconActive
)}>
<InverseIcon />
</div>
)}
{children}
</button>
)
}
)
export default SimpleButton export default SimpleButton

View file

@ -1,10 +1,35 @@
import * as R from 'ramda'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { Td, THead } from 'src/components/fake-table/Table' import {
Td,
THead,
TDoubleLevelHead,
ThDoubleLevel
} from 'src/components/fake-table/Table'
import { startCase } from 'src/utils/string' import { startCase } from 'src/utils/string'
import TableCtx from './Context' import TableCtx from './Context'
const groupSecondHeader = elements => {
const [toSHeader, noSHeader] = R.partition(R.has('doubleHeader'))(elements)
if (!toSHeader.length) {
return [elements, THead]
}
const index = R.indexOf(toSHeader[0], elements)
const width = R.compose(R.sum, R.map(R.path(['width'])))(toSHeader)
const innerElements = R.insert(
index,
{ width, elements: toSHeader, name: toSHeader[0].doubleHeader },
noSHeader
)
return [innerElements, TDoubleLevelHead]
}
const Header = () => { const Header = () => {
const { const {
elements, elements,
@ -16,15 +41,35 @@ const Header = () => {
toggleWidth, toggleWidth,
DEFAULT_COL_SIZE DEFAULT_COL_SIZE
} = useContext(TableCtx) } = useContext(TableCtx)
const mapElement2 = (it, idx) => {
const { width, elements, name } = it
if (elements && elements.length) {
return (
<ThDoubleLevel key={idx} width={width} title={name}>
{elements.map(mapElement)}
</ThDoubleLevel>
)
}
return mapElement(it, idx)
}
const mapElement = (
{ name, width = DEFAULT_COL_SIZE, header, textAlign },
idx
) => (
<Td header key={idx} width={width} textAlign={textAlign}>
{header || startCase(name)}
</Td>
)
const [innerElements, HeaderElement] = groupSecondHeader(elements)
return ( return (
<THead> <HeaderElement>
{elements.map( {innerElements.map(mapElement2)}
({ name, width = DEFAULT_COL_SIZE, header, textAlign }, idx) => (
<Td header key={idx} width={width} textAlign={textAlign}>
{header || startCase(name)}
</Td>
)
)}
{enableEdit && ( {enableEdit && (
<Td header width={editWidth} textAlign="center"> <Td header width={editWidth} textAlign="center">
Edit Edit
@ -40,7 +85,7 @@ const Header = () => {
Enable Enable
</Td> </Td>
)} )}
</THead> </HeaderElement>
) )
} }

View file

@ -83,7 +83,7 @@ const ActionCol = ({ disabled, editing }) => {
) )
} }
const ECol = ({ editing, config }) => { const ECol = ({ editing, config, extraPaddingRight, extraPaddingLeft }) => {
const { const {
name, name,
input, input,
@ -115,7 +115,11 @@ const ECol = ({ editing, config }) => {
return ( return (
<Td <Td
className={{ [classes.withSuffix]: suffix }} className={{
[classes.extraPaddingRight]: extraPaddingRight,
[classes.extraPaddingLeft]: extraPaddingLeft,
[classes.withSuffix]: suffix
}}
width={width} width={width}
size={size} size={size}
bold={bold} bold={bold}
@ -162,13 +166,31 @@ const ERow = ({ editing, disabled }) => {
const shouldStripe = stripeWhen && stripeWhen(values) && !editing const shouldStripe = stripeWhen && stripeWhen(values) && !editing
const innerElements = shouldStripe ? groupStriped(elements) : elements const innerElements = shouldStripe ? groupStriped(elements) : elements
const [toSHeader] = R.partition(R.has('doubleHeader'))(elements)
const extraPaddingLeftIndex = toSHeader?.length
? R.indexOf(toSHeader[0], elements)
: -1
const extraPaddingRightIndex = toSHeader?.length
? R.indexOf(toSHeader[toSHeader.length - 1], elements)
: -1
return ( return (
<Tr <Tr
size={rowSize} size={rowSize}
error={errors && errors.length} error={errors && errors.length}
errorMessage={errors && errors.toString()}> errorMessage={errors && errors.toString()}>
{innerElements.map((it, idx) => { {innerElements.map((it, idx) => {
return <ECol key={idx} config={it} editing={editing} /> return (
<ECol
key={idx}
config={it}
editing={editing}
extraPaddingRight={extraPaddingRightIndex === idx}
extraPaddingLeft={extraPaddingLeftIndex === idx}
/>
)
})} })}
{(enableEdit || enableDelete || enableToggle) && ( {(enableEdit || enableDelete || enableToggle) && (
<ActionCol disabled={disabled} editing={editing} /> <ActionCol disabled={disabled} editing={editing} />

View file

@ -4,11 +4,20 @@ export default {
cancelButton: { cancelButton: {
marginRight: 20 marginRight: 20
}, },
withSuffix: ({ textAlign }) => ({ extraPaddingLeft: {
display: 'flex', paddingLeft: 35
alignItems: 'baseline', },
justifyContent: textAlign === 'right' && 'end' extraPaddingRight: {
}), paddingRight: 45
},
withSuffix: ({ textAlign }) => {
const justifyContent = textAlign === 'right' ? 'end' : textAlign
return {
display: 'flex',
alignItems: 'baseline',
justifyContent
}
},
suffix: { suffix: {
marginLeft: 7 marginLeft: 7
}, },

View file

@ -65,8 +65,8 @@ const Th = ({ children, ...props }) => {
) )
} }
const ThDoubleLevel = ({ title, children, className }) => { const ThDoubleLevel = ({ title, children, className, width }) => {
const classes = useStyles() const classes = useStyles({ width })
return ( return (
<div className={classnames(className, classes.thDoubleLevel)}> <div className={classnames(className, classes.thDoubleLevel)}>

View file

@ -31,11 +31,13 @@ export default {
color: white, color: white,
display: 'table-row' display: 'table-row'
}, },
thDoubleLevel: { thDoubleLevel: ({ width }) => ({
padding: [[0, spacer * 2]], width,
display: 'table-cell', display: 'table-cell',
'& > :first-child': { '& > :first-child': {
margin: [[0, 10]],
extend: label1, extend: label1,
fontWeight: 700,
display: 'flex', display: 'flex',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
@ -45,14 +47,12 @@ export default {
height: 28 height: 28
}, },
'& > :last-child': { '& > :last-child': {
padding: [[0, 11]],
display: 'table-cell', display: 'table-cell',
verticalAlign: 'middle', verticalAlign: 'middle',
height: tableDoubleHeaderHeight - 28, height: tableDoubleHeaderHeight - 28
'& > div': {
display: 'inline-block'
}
} }
}, }),
cellDoubleLevel: { cellDoubleLevel: {
display: 'flex', display: 'flex',
padding: [[0, spacer * 2]] padding: [[0, spacer * 2]]

View file

@ -1,10 +1,10 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core'
import classnames from 'classnames' import classnames from 'classnames'
import { useSelect } from 'downshift' import { useSelect } from 'downshift'
import React from 'react' import React from 'react'
import { ReactComponent as Arrowdown } from '../../../styling/icons/action/arrow/regular.svg' import { ReactComponent as Arrowdown } from 'src/styling/icons/action/arrow/regular.svg'
import { startCase } from '../../../utils/string' import { startCase } from 'src/utils/string'
import styles from './Select.styles' import styles from './Select.styles'

View file

@ -2,7 +2,10 @@ import { bySize, bold } from 'src/styling/helpers'
import { secondaryColor } from 'src/styling/variables' import { secondaryColor } from 'src/styling/variables'
export default { export default {
size: ({ size }) => bySize(size), size: ({ size }) => ({
marginTop: size === 'lg' ? -2 : 0,
...bySize(size)
}),
bold, bold,
root: ({ width, textAlign }) => ({ root: ({ width, textAlign }) => ({
width, width,

View file

@ -13,7 +13,6 @@ const RadioGroupFormik = memo(({ label, ...props }) => {
options={props.options} options={props.options}
ariaLabel={name} ariaLabel={name}
onChange={e => { onChange={e => {
console.log(e)
onChange(e) onChange(e)
props.resetError && props.resetError() props.resetError && props.resetError()
}} }}

View file

@ -1,6 +1,7 @@
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 TextInput from './TextInput' import TextInput from './TextInput'
export { Autocomplete, Checkbox, TextInput, RadioGroup } export { Autocomplete, Checkbox, TextInput, SecretInput, RadioGroup }

View file

@ -29,7 +29,7 @@ const Sidebar = ({
[classes.link]: true [classes.link]: true
})} })}
onClick={() => onClick(it)}> onClick={() => onClick(it)}>
{itemRender ? itemRender(it) : displayName(it)} {itemRender ? itemRender(it, isSelected(it)) : displayName(it)}
</div> </div>
))} ))}
{children} {children}

View file

@ -1,4 +1,5 @@
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import classnames from 'classnames'
import React from 'react' import React from 'react'
import ErrorMessage from 'src/components/ErrorMessage' import ErrorMessage from 'src/components/ErrorMessage'
@ -8,10 +9,10 @@ import styles from './TitleSection.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const TitleSection = ({ title, error, labels }) => { const TitleSection = ({ className, title, error, labels, children }) => {
const classes = useStyles() const classes = useStyles()
return ( return (
<div className={classes.titleWrapper}> <div className={classnames(classes.titleWrapper, className)}>
<div className={classes.titleAndButtonsContainer}> <div className={classes.titleAndButtonsContainer}>
<Title>{title}</Title> <Title>{title}</Title>
{error && ( {error && (
@ -19,6 +20,7 @@ const TitleSection = ({ title, error, labels }) => {
)} )}
</div> </div>
<div className={classes.headerLabels}>{labels}</div> <div className={classes.headerLabels}>{labels}</div>
{children}
</div> </div>
) )
} }

View file

@ -2,6 +2,7 @@ import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames' import classnames from 'classnames'
import React, { memo } from 'react' import React, { memo } from 'react'
import typographyStyles from 'src/components/typography/styles'
import { import {
tableCellColor, tableCellColor,
tableCellHeight, tableCellHeight,
@ -10,7 +11,6 @@ import {
tableErrorColor, tableErrorColor,
tableSuccessColor tableSuccessColor
} from 'src/styling/variables' } from 'src/styling/variables'
import typographyStyles from 'src/components/typography/styles'
const { info2, p } = typographyStyles const { info2, p } = typographyStyles
@ -19,8 +19,7 @@ const useStyles = makeStyles({
extend: p, extend: p,
padding: 4, padding: 4,
height: tableCellHeight, height: tableCellHeight,
backgroundColor: tableCellColor, backgroundColor: tableCellColor
boxShadow: '0 0 4px 0 rgba(0, 0, 0, 0.08)'
}, },
lg: { lg: {
extend: info2, extend: info2,

View file

@ -6,37 +6,62 @@ import styles from './styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
function H1({ children, className, ...props }) { function H1({ children, noMargin, className, ...props }) {
const classes = useStyles() const classes = useStyles()
const classNames = {
[classes.h1]: true,
[classes.noMargin]: noMargin,
[className]: !!className
}
return ( return (
<h1 className={classnames(classes.h1, className)} {...props}> <h1 className={classnames(classNames)} {...props}>
{children} {children}
</h1> </h1>
) )
} }
function H2({ children, className, ...props }) { function H2({ children, noMargin, className, ...props }) {
const classes = useStyles() const classes = useStyles()
const classNames = {
[classes.h2]: true,
[classes.noMargin]: noMargin,
[className]: !!className
}
return ( return (
<h2 className={classnames(classes.h2, className)} {...props}> <h2 className={classnames(classNames)} {...props}>
{children} {children}
</h2> </h2>
) )
} }
function H3({ children, className, ...props }) { function H3({ children, noMargin, className, ...props }) {
const classes = useStyles() const classes = useStyles()
const classNames = {
[classes.h3]: true,
[classes.noMargin]: noMargin,
[className]: !!className
}
return ( return (
<h3 className={classnames(classes.h3, className)} {...props}> <h3 className={classnames(classNames)} {...props}>
{children} {children}
</h3> </h3>
) )
} }
function H4({ children, className, ...props }) { function H4({ children, noMargin, className, ...props }) {
const classes = useStyles() const classes = useStyles()
console.log(className)
const classNames = {
[classes.h4]: true,
[classes.noMargin]: noMargin,
[className]: !!className
}
return ( return (
<h4 className={classnames(classes.h4, className)} {...props}> <h4 className={classnames(classNames)} {...props}>
{children} {children}
</h4> </h4>
) )
@ -57,13 +82,13 @@ function pBuilder(elementClass) {
return ({ inline, noMargin, className, children, ...props }) => { return ({ inline, noMargin, className, children, ...props }) => {
const classes = useStyles() const classes = useStyles()
const classNames = { const classNames = {
[className]: !!className,
[classes[elementClass]]: elementClass, [classes[elementClass]]: elementClass,
className: true,
[classes.inline]: inline, [classes.inline]: inline,
[classes.noMarginP]: noMargin [classes.noMargin]: noMargin
} }
return ( return (
<p className={classnames(classNames, className)} {...props}> <p className={classnames(classNames)} {...props}>
{children} {children}
</p> </p>
) )

View file

@ -122,7 +122,7 @@ export default {
inline: { inline: {
display: 'inline' display: 'inline'
}, },
noMarginP: { noMargin: {
margin: 0 margin: 0
} }
} }

View file

@ -1,9 +1,9 @@
import { useMutation } from '@apollo/react-hooks' import { useMutation } from '@apollo/react-hooks'
import { Dialog, DialogContent, SvgIcon, IconButton } from '@material-ui/core' import { Dialog, DialogContent, SvgIcon, IconButton } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import classnames from 'classnames' import classnames from 'classnames'
import { Form, Formik, FastField } from 'formik' import { Form, Formik, FastField } from 'formik'
import gql from 'graphql-tag'
import QRCode from 'qrcode.react' import QRCode from 'qrcode.react'
import React, { memo, useState } from 'react' import React, { memo, useState } from 'react'
import * as Yup from 'yup' import * as Yup from 'yup'

View file

@ -1,15 +1,30 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost' import { makeStyles } from '@material-ui/core/styles'
import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import HelpTooltip from 'src/components/HelpTooltip'
import { NamespacedTable as EditableTable } from 'src/components/editableTable' import { NamespacedTable as EditableTable } from 'src/components/editableTable'
import { Switch } from 'src/components/inputs'
import TitleSection from 'src/components/layout/TitleSection' import TitleSection from 'src/components/layout/TitleSection'
import { P, Label2 } from 'src/components/typography'
import { fromNamespace, toNamespace } from 'src/utils/config' import { fromNamespace, toNamespace } from 'src/utils/config'
import Wizard from './Wizard' import Wizard from './Wizard'
import { DenominationsSchema, getElements } from './helper' import { DenominationsSchema, getElements } from './helper'
const useStyles = makeStyles({
fudgeFactor: {
display: 'flex',
alignItems: 'center',
marginRight: 156
},
switchLabel: {
margin: 6
}
})
const SAVE_CONFIG = gql` const SAVE_CONFIG = gql`
mutation Save($config: JSONObject) { mutation Save($config: JSONObject) {
saveConfig(config: $config) saveConfig(config: $config)
@ -30,6 +45,7 @@ const GET_INFO = gql`
` `
const CashOut = ({ name: SCREEN_KEY }) => { const CashOut = ({ name: SCREEN_KEY }) => {
const classes = useStyles()
const [wizard, setWizard] = useState(false) const [wizard, setWizard] = useState(false)
const [error, setError] = useState(false) const [error, setError] = useState(false)
const { data } = useQuery(GET_INFO) const { data } = useQuery(GET_INFO)
@ -47,6 +63,7 @@ const CashOut = ({ name: SCREEN_KEY }) => {
} }
const config = data?.config && fromNamespace(SCREEN_KEY)(data.config) const config = data?.config && fromNamespace(SCREEN_KEY)(data.config)
const fudgeFactorActive = config?.fudgeFactorActive ?? false
const locale = data?.config && fromNamespace('locale')(data.config) const locale = data?.config && fromNamespace('locale')(data.config)
const machines = data?.machines ?? [] const machines = data?.machines ?? []
@ -58,7 +75,31 @@ const CashOut = ({ name: SCREEN_KEY }) => {
return ( return (
<> <>
<TitleSection title="Cash-out" error={error} /> <TitleSection title="Cash-out" error={error}>
<div className={classes.fudgeFactor}>
<P>Transaction fudge factor</P>
<Switch
checked={fudgeFactorActive}
onChange={event => {
save({ fudgeFactorActive: event.target.checked })
}}
value={fudgeFactorActive}
/>
<Label2 className={classes.switchLabel}>
{fudgeFactorActive ? 'On' : 'Off'}
</Label2>
<HelpTooltip width={304}>
<P>
Automatically accept customer deposits as complete if their
received amount is 10 crypto atoms or less.
</P>
<P>
(Crypto atoms are the smallest unit in each cryptocurrency. E.g.,
satoshis in Bitcoin, or wei in Ethereum.)
</P>
</HelpTooltip>
</div>
</TitleSection>
<EditableTable <EditableTable
name="test" name="test"
namespaces={R.map(R.path(['deviceId']))(machines)} namespaces={R.map(R.path(['deviceId']))(machines)}

View file

@ -57,14 +57,13 @@ const WizardStep = ({
</div> </div>
</Form> </Form>
</Formik> </Formik>
// TODO: there was a disabled link here showing the currency code; restore it
)} )}
{lastStep && ( {lastStep && (
<> <>
<P> <P>
When enabling cash out, your bill count will be authomatically set When enabling cash out, your bill count will be automatically set to
to zero. Make sure you physically put cash inside the cashboxes to zero. Make sure you physically put cash inside the cashboxes to
allow the machine to dispense it to your users. If you already did, allow the machine to dispense it to your users. If you already did,
make sure you set the correct cash out bill count for this machine make sure you set the correct cash out bill count for this machine
on your Cashboxes tab under Maintenance. on your Cashboxes tab under Maintenance.
@ -72,7 +71,7 @@ const WizardStep = ({
<P> <P>
When enabling cash out, default commissions will be set. To change When enabling cash out, default commissions will be set. To change
commissions for this machine, please go to the Commissions tab under commissions for this machine, please go to the Commissions tab under
Settings. where you can set exceptions for each of the available Settings where you can set exceptions for each of the available
cryptocurrencies. cryptocurrencies.
</P> </P>
<div className={classes.submit}> <div className={classes.submit}>

View file

@ -13,7 +13,7 @@ const getElements = (machines, { fiatCurrency } = {}) => {
{ {
name: 'id', name: 'id',
header: 'Machine', header: 'Machine',
width: 254, width: 200,
view: it => machines.find(({ deviceId }) => deviceId === it).name, view: it => machines.find(({ deviceId }) => deviceId === it).name,
size: 'sm', size: 'sm',
editable: false editable: false
@ -24,7 +24,8 @@ const getElements = (machines, { fiatCurrency } = {}) => {
view: it => `${it} ${fiatCurrency}`, view: it => `${it} ${fiatCurrency}`,
size: 'sm', size: 'sm',
stripe: true, stripe: true,
width: 265, width: 200,
textAlign: 'right',
input: TextInput input: TextInput
}, },
{ {
@ -33,7 +34,8 @@ const getElements = (machines, { fiatCurrency } = {}) => {
view: it => `${it} ${fiatCurrency}`, view: it => `${it} ${fiatCurrency}`,
size: 'sm', size: 'sm',
stripe: true, stripe: true,
width: 265, textAlign: 'right',
width: 200,
input: TextInput input: TextInput
}, },
{ {
@ -41,6 +43,7 @@ const getElements = (machines, { fiatCurrency } = {}) => {
header: '0-conf Limit', header: '0-conf Limit',
size: 'sm', size: 'sm',
stripe: true, stripe: true,
textAlign: 'right',
width: 200, width: 200,
input: TextInput input: TextInput
} }

View file

@ -1,12 +1,12 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React from 'react' 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 { fromNamespace, toNamespace } from 'src/utils/config' import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
import { import {
mainFields, mainFields,
@ -44,6 +44,9 @@ const Commissions = ({ name: SCREEN_KEY }) => {
}) })
const config = data?.config && fromNamespace(SCREEN_KEY)(data.config) const config = data?.config && fromNamespace(SCREEN_KEY)(data.config)
const currency = R.path(['fiatCurrency'])(
fromNamespace(namespaces.LOCALE)(data?.config)
)
const commission = config && !R.isEmpty(config) ? config : defaults const commission = config && !R.isEmpty(config) ? config : defaults
@ -71,7 +74,7 @@ const Commissions = ({ name: SCREEN_KEY }) => {
save={save} save={save}
validationSchema={schema} validationSchema={schema}
data={R.of(commission)} data={R.of(commission)}
elements={mainFields(data)} elements={mainFields(currency)}
/> />
</Section> </Section>
<Section> <Section>
@ -86,7 +89,7 @@ const Commissions = ({ name: SCREEN_KEY }) => {
save={saveOverrides} save={saveOverrides}
validationSchema={OverridesSchema} validationSchema={OverridesSchema}
data={commission.overrides ?? []} data={commission.overrides ?? []}
elements={overrides(data)} elements={overrides(data, currency)}
/> />
</Section> </Section>
</> </>

View file

@ -4,7 +4,7 @@ import * as Yup from 'yup'
import { TextInput } from 'src/components/inputs/formik' import { TextInput } from 'src/components/inputs/formik'
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js' import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
const getOverridesFields = getData => { const getOverridesFields = (getData, currency) => {
const getView = (data, code, compare) => it => { const getView = (data, code, compare) => it => {
if (!data) return '' if (!data) return ''
@ -39,7 +39,7 @@ const getOverridesFields = getData => {
}, },
{ {
name: 'cryptoCurrencies', name: 'cryptoCurrencies',
width: 270, width: 250,
size: 'sm', size: 'sm',
view: displayCodeArray(cryptoData), view: displayCodeArray(cryptoData),
input: Autocomplete, input: Autocomplete,
@ -54,69 +54,91 @@ const getOverridesFields = getData => {
name: 'cashIn', name: 'cashIn',
display: 'Cash-in', display: 'Cash-in',
width: 140, width: 140,
input: TextInput input: TextInput,
textAlign: 'right',
suffix: '%'
}, },
{ {
name: 'cashOut', name: 'cashOut',
display: 'Cash-out', display: 'Cash-out',
width: 140, width: 140,
input: TextInput input: TextInput,
textAlign: 'right',
suffix: '%'
}, },
{ {
name: 'fixedFee', name: 'fixedFee',
display: 'Fixed fee', display: 'Fixed fee',
width: 140, width: 140,
input: TextInput input: TextInput,
doubleHeader: 'Cash-in only',
textAlign: 'right',
suffix: currency
}, },
{ {
name: 'minimumTx', name: 'minimumTx',
display: 'Minimun Tx', display: 'Minimun Tx',
width: 140, width: 140,
input: TextInput input: TextInput,
doubleHeader: 'Cash-in only',
textAlign: 'right',
suffix: currency
} }
] ]
} }
const mainFields = auxData => [ const mainFields = currency => [
{ {
name: 'cashIn', name: 'cashIn',
display: 'Cash-in', display: 'Cash-in',
width: 169, width: 169,
size: 'lg', size: 'lg',
input: TextInput input: TextInput,
suffix: '%'
}, },
{ {
name: 'cashOut', name: 'cashOut',
display: 'Cash-out', display: 'Cash-out',
width: 169, width: 169,
size: 'lg', size: 'lg',
input: TextInput input: TextInput,
suffix: '%'
}, },
{ {
name: 'fixedFee', name: 'fixedFee',
display: 'Fixed fee', display: 'Fixed fee',
width: 169, width: 169,
size: 'lg', size: 'lg',
input: TextInput doubleHeader: 'Cash-in only',
textAlign: 'center',
input: TextInput,
suffix: currency
}, },
{ {
name: 'minimumTx', name: 'minimumTx',
display: 'Minimun Tx', display: 'Minimun Tx',
width: 169, width: 169,
size: 'lg', size: 'lg',
input: TextInput doubleHeader: 'Cash-in only',
textAlign: 'center',
input: TextInput,
suffix: currency
} }
] ]
const overrides = auxData => { const overrides = (auxData, currency) => {
const getData = R.path(R.__, auxData) const getData = R.path(R.__, auxData)
return getOverridesFields(getData) return getOverridesFields(getData, currency)
} }
const schema = Yup.object().shape({ const schema = Yup.object().shape({
cashIn: Yup.number().required('Required'), cashIn: Yup.number()
cashOut: Yup.number().required('Required'), .max(100)
.required('Required'),
cashOut: Yup.number()
.max(100)
.required('Required'),
fixedFee: Yup.number().required('Required'), fixedFee: Yup.number().required('Required'),
minimumTx: Yup.number().required('Required') minimumTx: Yup.number().required('Required')
}) })
@ -124,8 +146,12 @@ const schema = Yup.object().shape({
const OverridesSchema = Yup.object().shape({ const OverridesSchema = Yup.object().shape({
machine: Yup.string().required('Required'), machine: Yup.string().required('Required'),
cryptoCurrencies: Yup.array().required('Required'), cryptoCurrencies: Yup.array().required('Required'),
cashIn: Yup.number().required('Required'), cashIn: Yup.number()
cashOut: Yup.number().required('Required'), .max(100)
.required('Required'),
cashOut: Yup.number()
.max(100)
.required('Required'),
fixedFee: Yup.number().required('Required'), fixedFee: Yup.number().required('Required'),
minimumTx: Yup.number().required('Required') minimumTx: Yup.number().required('Required')
}) })

View file

@ -1,23 +1,23 @@
import { makeStyles } from '@material-ui/core/styles' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles, Breadcrumbs, Box } from '@material-ui/core'
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { memo } from 'react' import React, { memo } from 'react'
import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
import { useHistory, useParams } from 'react-router-dom' import { useHistory, useParams } from 'react-router-dom'
import Breadcrumbs from '@material-ui/core/Breadcrumbs'
import NavigateNextIcon from '@material-ui/icons/NavigateNext'
import { ActionButton } from 'src/components/buttons'
import { Label1, Label2 } from 'src/components/typography'
import { import {
OVERRIDE_AUTHORIZED, OVERRIDE_AUTHORIZED,
OVERRIDE_REJECTED OVERRIDE_REJECTED
} from 'src/pages/Customers/components/propertyCard' } from 'src/pages/Customers/components/propertyCard'
import { ActionButton } from 'src/components/buttons'
import { Label1 } from 'src/components/typography'
import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/block/white.svg'
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/zodiac.svg'
import { ReactComponent as AuthorizeReversedIcon } from 'src/styling/icons/button/authorize/white.svg' import { ReactComponent as AuthorizeReversedIcon } 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'
import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/block/white.svg'
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/zodiac.svg'
import styles from './CustomerProfile.styles'
import { import {
CustomerDetails, CustomerDetails,
IdDataCard, IdDataCard,
@ -25,9 +25,8 @@ import {
IdCardPhotoCard, IdCardPhotoCard,
TransactionsList TransactionsList
} from './components' } from './components'
import { mainStyles } from './Customers.styles'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(styles)
const GET_CUSTOMER = gql` const GET_CUSTOMER = gql`
query customer($customerId: ID!) { query customer($customerId: ID!) {
@ -96,8 +95,7 @@ const CustomerProfile = memo(() => {
const { data: customerResponse, refetch: getCustomer } = useQuery( const { data: customerResponse, refetch: getCustomer } = useQuery(
GET_CUSTOMER, GET_CUSTOMER,
{ {
variables: { customerId }, variables: { customerId }
fetchPolicy: 'no-cache'
} }
) )
@ -125,24 +123,25 @@ const CustomerProfile = memo(() => {
return ( return (
<> <>
<Breadcrumbs <Breadcrumbs
classes={{ root: classes.breadcrumbs }}
separator={<NavigateNextIcon fontSize="small" />} separator={<NavigateNextIcon fontSize="small" />}
aria-label="breadcrumb"> aria-label="breadcrumb">
<Label1 <Label1
noMargin
className={classes.labelLink} className={classes.labelLink}
onClick={() => history.push('/compliance/customers')}> onClick={() => history.push('/compliance/customers')}>
Customers Customers
</Label1> </Label1>
<Label1 className={classes.bold}> <Label2 noMargin className={classes.labelLink}>
{R.path(['name'])(customerData)} Rafael{R.path(['name'])(customerData)}
</Label1> </Label2>
</Breadcrumbs> </Breadcrumbs>
<div> <div>
<div className={classes.header}> <Box display="flex" justifyContent="space-between">
<CustomerDetails customer={customerData} /> <CustomerDetails customer={customerData} />
<div className={classes.rightAligned}> <div>
<Label1 className={classes.label1}>Actions</Label1> <Label1 className={classes.actionLabel}>Actions</Label1>
<ActionButton <ActionButton
className={classes.actionButton}
color="primary" color="primary"
Icon={blocked ? AuthorizeIcon : BlockIcon} Icon={blocked ? AuthorizeIcon : BlockIcon}
InverseIcon={blocked ? AuthorizeReversedIcon : BlockReversedIcon} InverseIcon={blocked ? AuthorizeReversedIcon : BlockReversedIcon}
@ -156,8 +155,8 @@ const CustomerProfile = memo(() => {
{`${blocked ? 'Authorize' : 'Block'} customer`} {`${blocked ? 'Authorize' : 'Block'} customer`}
</ActionButton> </ActionButton>
</div> </div>
</div> </Box>
<div className={classes.rowCenterAligned}> <Box display="flex">
<IdDataCard <IdDataCard
customerData={customerData} customerData={customerData}
updateCustomer={updateCustomer} updateCustomer={updateCustomer}
@ -170,7 +169,7 @@ const CustomerProfile = memo(() => {
customerData={customerData} customerData={customerData}
updateCustomer={updateCustomer} updateCustomer={updateCustomer}
/> />
</div> </Box>
</div> </div>
<TransactionsList data={transactionsData} /> <TransactionsList data={transactionsData} />
</> </>

View file

@ -0,0 +1,15 @@
import { comet } from 'src/styling/variables'
export default {
labelLink: {
cursor: 'pointer',
color: comet
},
breadcrumbs: {
margin: [[20, 0]]
},
actionLabel: {
color: comet,
margin: [[4, 0]]
}
}

View file

@ -1,5 +1,5 @@
import { useQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React from 'react' import React from 'react'
import { useHistory } from 'react-router-dom' import { useHistory } from 'react-router-dom'

View file

@ -9,9 +9,9 @@ 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'
import { mainStyles } from './Customers.styles' import styles from './CustomersList.styles'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(styles)
const CustomersList = ({ data, onClick }) => { const CustomersList = ({ data, onClick }) => {
const classes = useStyles() const classes = useStyles()

View file

@ -1,39 +1,17 @@
import typographyStyles from 'src/components/typography/styles' import typographyStyles from 'src/components/typography/styles'
import baseStyles from 'src/pages/Logs.styles' import baseStyles from 'src/pages/Logs.styles'
import { zircon, primaryColor, comet } from 'src/styling/variables' import { zircon, primaryColor } from 'src/styling/variables'
const { label1 } = typographyStyles const { label1 } = typographyStyles
const { titleWrapper, titleAndButtonsContainer } = baseStyles const { titleWrapper, titleAndButtonsContainer } = baseStyles
const mainStyles = { export default {
rightAligned: {
display: 'flex',
flexFlow: 'column nowrap',
right: 0
},
actionButton: {
height: 28
},
titleWrapper, titleWrapper,
titleAndButtonsContainer, titleAndButtonsContainer,
header: {
display: 'flex',
flex: 1,
justifyContent: 'space-between'
},
customerDetails: {
display: 'flex',
flex: 1
},
row: { row: {
display: 'flex', display: 'flex',
flexFlow: 'row nowrap' flexFlow: 'row nowrap'
}, },
rowCenterAligned: {
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center'
},
rowSpaceBetween: { rowSpaceBetween: {
display: 'flex', display: 'flex',
flexFlow: 'row nowrap', flexFlow: 'row nowrap',
@ -50,17 +28,6 @@ const mainStyles = {
textInput: { textInput: {
width: 144 width: 144
}, },
label1: {
fontFamily: 'MuseoSans',
fontSize: 12,
fontWeight: 500,
fontStretch: 'normal',
fontStyle: 'normal',
lineHeight: 1.33,
letterSpacing: 'normal',
color: comet,
margin: [[4, 0]]
},
p: { p: {
fontFamily: 'MuseoSans', fontFamily: 'MuseoSans',
fontSize: 14, fontSize: 14,
@ -71,9 +38,6 @@ const mainStyles = {
letterSpacing: 'normal', letterSpacing: 'normal',
color: primaryColor color: primaryColor
}, },
bold: {
fontWeight: 'bold'
},
txId: { txId: {
fontFamily: 'MuseoSans', fontFamily: 'MuseoSans',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
@ -106,7 +70,7 @@ const mainStyles = {
height: 92, height: 92,
borderRadius: 8, borderRadius: 8,
backgroundColor: zircon, backgroundColor: zircon,
margin: [[15, 28, 0, 0]], margin: [[0, 28, 0, 0]],
padding: [[30]], padding: [[30]],
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between' justifyContent: 'space-between'
@ -124,9 +88,6 @@ const mainStyles = {
height: 240, height: 240,
margin: [[32, 0, 0, 0]] margin: [[32, 0, 0, 0]]
}, },
labelLink: {
cursor: 'pointer'
},
field: { field: {
position: 'relative', position: 'relative',
width: 144, width: 144,
@ -158,5 +119,3 @@ const mainStyles = {
maxHeight: 97 maxHeight: 97
} }
} }
export { mainStyles }

View file

@ -1,18 +1,18 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles, Box } from '@material-ui/core'
import * as R from 'ramda'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda'
import React, { memo } from 'react' import React, { memo } from 'react'
import { H2 } from 'src/components/typography' import { H2 } from 'src/components/typography'
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'
import { mainStyles } from '../Customers.styles'
import { ifNotNull } from '../../../utils/nullCheck' import { ifNotNull } from '../../../utils/nullCheck'
import styles from '../CustomersList.styles'
import FrontCameraPhoto from './FrontCameraPhoto' import FrontCameraPhoto from './FrontCameraPhoto'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(styles)
const CustomerDetails = memo(({ customer }) => { const CustomerDetails = memo(({ customer }) => {
const classes = useStyles() const classes = useStyles()
@ -61,30 +61,30 @@ const CustomerDetails = memo(({ customer }) => {
] ]
return ( return (
<div className={classes.customerDetails}> <Box display="flex">
<FrontCameraPhoto <FrontCameraPhoto
frontCameraPath={R.path(['frontCameraPath'])(customer)} frontCameraPath={R.path(['frontCameraPath'])(customer)}
/> />
<div> <div>
<div className={classes.rowCenterAligned}> <Box display="flex">
<H2 className={classes.customerName}>{R.path(['name'])(customer)}</H2> <H2 noMargin>Rafael{R.path(['name'])(customer)}</H2>
</div> </Box>
<div className={classes.rowCenterAligned}> <Box display="flex">
{elements.map(({ size, header }, idx) => ( {elements.map(({ size, header }, idx) => (
<div key={idx} className={classes.label1} style={{ width: size }}> <div key={idx} className={classes.label1} style={{ width: size }}>
{header} {header}
</div> </div>
))} ))}
</div> </Box>
<div className={classes.rowCenterAligned}> <Box display="flex">
{elements.map(({ size, value }, idx) => ( {elements.map(({ size, value }, idx) => (
<div key={idx} className={classes.p} style={{ width: size }}> <div key={idx} className={classes.p} style={{ width: size }}>
{value} {value}
</div> </div>
))} ))}
</div> </Box>
</div> </div>
</div> </Box>
) )
}) })

View file

@ -3,7 +3,7 @@ import React, { memo } from 'react'
import { Info3, Label1 } from 'src/components/typography' import { Info3, Label1 } from 'src/components/typography'
import { mainStyles } from '../Customers.styles' import mainStyles from '../CustomersList.styles'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(mainStyles)

View file

@ -1,12 +1,11 @@
import { Paper } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import React, { memo } from 'react' import React, { memo } from 'react'
import { Paper } from '@material-ui/core'
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg' import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
import { URI } from 'src/utils/apollo'
import { mainStyles } from '../Customers.styles' import mainStyles from '../CustomersList.styles'
import { IMAGES_URI } from './variables'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(mainStyles)
@ -16,10 +15,7 @@ const FrontCameraPhoto = memo(({ frontCameraPath }) => {
return ( return (
<Paper className={classes.photo} elevation={0}> <Paper className={classes.photo} elevation={0}>
{frontCameraPath ? ( {frontCameraPath ? (
<img <img src={`${URI}/front-camera-photo/${frontCameraPath}`} alt="" />
src={`${IMAGES_URI}/front-camera-photo/${frontCameraPath}`}
alt=""
/>
) : ( ) : (
<CrossedCameraIcon /> <CrossedCameraIcon />
)} )}

View file

@ -1,19 +1,19 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import * as R from 'ramda'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda'
import React, { memo } from 'react' import React, { memo } from 'react'
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
import { import {
PropertyCard, PropertyCard,
OVERRIDE_AUTHORIZED, OVERRIDE_AUTHORIZED,
OVERRIDE_REJECTED OVERRIDE_REJECTED
} from 'src/pages/Customers/components/propertyCard' } from 'src/pages/Customers/components/propertyCard'
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
import { URI } from 'src/utils/apollo'
import { mainStyles } from '../Customers.styles' import mainStyles from '../CustomersList.styles'
import Field from './Field' import Field from './Field'
import { IMAGES_URI } from './variables'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(mainStyles)
@ -33,7 +33,7 @@ const IdCardPhotoCard = memo(({ customerData, updateCustomer }) => {
{customerData.idCardPhotoPath ? ( {customerData.idCardPhotoPath ? (
<img <img
className={classes.idCardPhoto} className={classes.idCardPhoto}
src={`${IMAGES_URI}/id-card-photo/${R.path(['idCardPhotoPath'])( src={`${URI}/id-card-photo/${R.path(['idCardPhotoPath'])(
customerData customerData
)}`} )}`}
alt="" alt=""

View file

@ -1,6 +1,6 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import * as R from 'ramda'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda'
import React, { memo } from 'react' import React, { memo } from 'react'
import { import {
@ -9,7 +9,7 @@ import {
OVERRIDE_REJECTED OVERRIDE_REJECTED
} from 'src/pages/Customers/components/propertyCard' } from 'src/pages/Customers/components/propertyCard'
import { mainStyles } from '../Customers.styles' import mainStyles from '../CustomersList.styles'
import Field from './Field' import Field from './Field'

View file

@ -1,7 +1,7 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
import * as R from 'ramda' import * as R from 'ramda'
import React, { memo } from 'react' import React, { memo } from 'react'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
import { import {
PropertyCard, PropertyCard,
@ -9,7 +9,7 @@ import {
OVERRIDE_REJECTED OVERRIDE_REJECTED
} from 'src/pages/Customers/components/propertyCard' } from 'src/pages/Customers/components/propertyCard'
import { mainStyles } from '../Customers.styles' import mainStyles from '../CustomersList.styles'
import Field from './Field' import Field from './Field'

View file

@ -10,7 +10,7 @@ import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-ou
import { toUnit } from 'src/utils/coin' import { toUnit } from 'src/utils/coin'
import CopyToClipboard from '../../Transactions/CopyToClipboard' import CopyToClipboard from '../../Transactions/CopyToClipboard'
import { mainStyles } from '../Customers.styles' import mainStyles from '../CustomersList.styles'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(mainStyles)

View file

@ -1,7 +1,7 @@
import { Paper } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames' import classnames from 'classnames'
import React, { memo } from 'react' import React, { memo } from 'react'
import { Paper } from '@material-ui/core'
import { ActionButton } from 'src/components/buttons' import { ActionButton } from 'src/components/buttons'
import { H3 } from 'src/components/typography' import { H3 } from 'src/components/typography'

View file

@ -1,4 +0,0 @@
const IMAGES_URI =
process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : ''
export { IMAGES_URI }

View file

@ -1,8 +1,8 @@
import { useQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classnames from 'classnames' import classnames from 'classnames'
import gql from 'graphql-tag'
import moment from 'moment' import moment from 'moment'
import QRCode from 'qrcode.react' import QRCode from 'qrcode.react'
import React, { useState } from 'react' import React, { useState } from 'react'
@ -35,6 +35,7 @@ const GET_FUNDING = gql`
{ {
funding { funding {
cryptoCode cryptoCode
errorMsg
fundingAddress fundingAddress
fundingAddressUrl fundingAddressUrl
confirmedBalance confirmedBalance
@ -55,6 +56,7 @@ const formatNumber = it => new BigNumber(it).toFormat(2)
const getConfirmedTotal = list => { const getConfirmedTotal = list => {
return formatNumber( return formatNumber(
list list
.filter(it => !it.errorMsg)
.map(it => new BigNumber(it.fiatConfirmedBalance)) .map(it => new BigNumber(it.fiatConfirmedBalance))
.reduce(sumReducer, new BigNumber(0)) .reduce(sumReducer, new BigNumber(0))
) )
@ -63,6 +65,7 @@ const getConfirmedTotal = list => {
const getPendingTotal = list => { const getPendingTotal = list => {
return formatNumber( return formatNumber(
list list
.filter(it => !it.errorMsg)
.map(it => new BigNumber(it.fiatPending)) .map(it => new BigNumber(it.fiatPending))
.reduce(sumReducer, new BigNumber(0)) .reduce(sumReducer, new BigNumber(0))
) )
@ -107,16 +110,29 @@ const Funding = () => {
setSelected(fundingResponse?.funding[0]) setSelected(fundingResponse?.funding[0])
} }
const itemRender = it => { const itemRender = (it, active) => {
const itemClass = {
[classes.item]: true,
[classes.inactiveItem]: !active
}
const wrapperClass = {
[classes.itemWrapper]: true,
[classes.error]: it.errorMsg
}
return ( return (
<div className={classes.itemWrapper}> <div className={classnames(wrapperClass)}>
<div className={classes.firstItem}>{it.display}</div> <div className={classes.firstItem}>{it.display}</div>
<div className={classes.item}> {!it.errorMsg && (
{it.fiatConfirmedBalance} {it.fiatCode} <>
</div> <div className={classnames(itemClass)}>
<div className={classes.item}> {it.fiatConfirmedBalance} {it.fiatCode}
{it.confirmedBalance} {it.cryptoCode} </div>
</div> <div className={classnames(itemClass)}>
{it.confirmedBalance} {it.cryptoCode}
</div>
</>
)}
</div> </div>
) )
} }
@ -149,7 +165,14 @@ const Funding = () => {
</div> </div>
)} )}
</Sidebar> </Sidebar>
{selected && !viewHistory && ( {selected && !viewHistory && selected.errorMsg && (
<div className={classes.main}>
<div className={classes.firstSide}>
<Info3 className={classes.error}>{selected.errorMsg}</Info3>
</div>
</div>
)}
{selected && !viewHistory && !selected.errorMsg && (
<div className={classes.main}> <div className={classes.main}>
<div className={classes.firstSide}> <div className={classes.firstSide}>
<H3>Balance ({selected.display})</H3> <H3>Balance ({selected.display})</H3>

View file

@ -3,7 +3,9 @@ import {
disabledColor2, disabledColor2,
spacer, spacer,
subheaderColor, subheaderColor,
placeholderColor errorColor,
placeholderColor,
comet
} from 'src/styling/variables' } from 'src/styling/variables'
const { label1 } = typographyStyles const { label1 } = typographyStyles
@ -25,6 +27,9 @@ export default {
secondSide: { secondSide: {
marginTop: -29 marginTop: -29
}, },
error: {
color: errorColor
},
coinTotal: { coinTotal: {
margin: `${spacer * 1.5}px 0` margin: `${spacer * 1.5}px 0`
}, },
@ -51,7 +56,11 @@ export default {
extend: label1, extend: label1,
margin: 2 margin: 2
}, },
inactiveItem: {
color: comet
},
firstItem: { firstItem: {
fontWeight: 700,
margin: 2 margin: 2
}, },
total: { total: {

View file

@ -1,5 +1,5 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React from 'react' import React from 'react'

View file

@ -37,6 +37,16 @@ export default {
fillColumn: { fillColumn: {
width: '100%' width: '100%'
}, },
shareButton: {
margin: 8,
display: 'flex',
alignItems: 'center',
fontSize: 12,
padding: [[0, 12]]
},
shareIcon: {
marginRight: 6
},
button: { button: {
margin: 8 margin: 8
}, },
@ -45,9 +55,9 @@ export default {
}, },
buttonsWrapper: { buttonsWrapper: {
display: 'flex', display: 'flex',
marginLeft: 10, marginLeft: 16,
'& > *': { '& > *': {
margin: 'auto 10px' margin: 'auto 6px'
} }
} }
} }

View file

@ -1,13 +1,13 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' 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 Title from 'src/components/Title' import Title from 'src/components/Title'
import { FeatureButton, SimpleButton } from 'src/components/buttons' import { SimpleButton } from 'src/components/buttons'
import Sidebar from 'src/components/layout/Sidebar' import Sidebar from 'src/components/layout/Sidebar'
import { import {
Table, Table,
@ -18,8 +18,8 @@ import {
TableCell TableCell
} from 'src/components/table' } from 'src/components/table'
import { Info3 } from 'src/components/typography' import { Info3 } from 'src/components/typography'
import { ReactComponent as DownloadActive } from 'src/styling/icons/button/download/white.svg' import { ReactComponent as WhiteShareIcon } from 'src/styling/icons/circle buttons/share/white.svg'
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg' import { ReactComponent as ShareIcon } from 'src/styling/icons/circle buttons/share/zodiac.svg'
import styles from './Logs.styles' import styles from './Logs.styles'
@ -62,7 +62,6 @@ const Logs = () => {
const [selected, setSelected] = useState(null) const [selected, setSelected] = useState(null)
const [saveMessage, setSaveMessage] = useState(null) const [saveMessage, setSaveMessage] = useState(null)
const [anchorEl, setAnchorEl] = useState(null)
const deviceId = selected?.deviceId const deviceId = selected?.deviceId
@ -76,7 +75,6 @@ const Logs = () => {
const { data: logsResponse } = useQuery(GET_MACHINE_LOGS, { const { data: logsResponse } = useQuery(GET_MACHINE_LOGS, {
variables: { deviceId }, variables: { deviceId },
fetchPolicy: 'no-cache',
skip: !selected, skip: !selected,
onCompleted: () => setSaveMessage('') onCompleted: () => setSaveMessage('')
}) })
@ -89,13 +87,6 @@ const Logs = () => {
return R.path(['deviceId'])(selected) === it.deviceId return R.path(['deviceId'])(selected) === it.deviceId
} }
const handleOpenRangePicker = event => {
setAnchorEl(anchorEl ? null : event.currentTarget)
}
const open = Boolean(anchorEl)
const id = open ? 'date-range-popover' : undefined
return ( return (
<> <>
<div className={classes.titleWrapper}> <div className={classes.titleWrapper}>
@ -103,25 +94,17 @@ const Logs = () => {
<Title>Machine Logs</Title> <Title>Machine Logs</Title>
{logsResponse && ( {logsResponse && (
<div className={classes.buttonsWrapper}> <div className={classes.buttonsWrapper}>
<FeatureButton
Icon={Download}
InverseIcon={DownloadActive}
aria-describedby={id}
variant="contained"
onClick={handleOpenRangePicker}
/>
<LogsDowloaderPopover <LogsDowloaderPopover
title="Download logs" title="Download logs"
name="machine-logs" name="machine-logs"
id={id}
open={open}
anchorEl={anchorEl}
logs={logsResponse.machineLogs} logs={logsResponse.machineLogs}
getTimestamp={log => log.timestamp} getTimestamp={log => log.timestamp}
/> />
<SimpleButton <SimpleButton
className={classes.button} className={classes.shareButton}
disabled={loading} disabled={loading}
Icon={ShareIcon}
InverseIcon={WhiteShareIcon}
onClick={sendSnapshot}> onClick={sendSnapshot}>
Share with Lamassu Share with Lamassu
</SimpleButton> </SimpleButton>

View file

@ -1,5 +1,5 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import React from 'react' import React from 'react'
import * as Yup from 'yup' import * as Yup from 'yup'

View file

@ -1,29 +1,20 @@
import { useMutation } from '@apollo/react-hooks' import { useMutation } from '@apollo/react-hooks'
import { Dialog, DialogContent } from '@material-ui/core' import { Dialog, DialogContent } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import classnames from 'classnames'
import moment from 'moment' import moment from 'moment'
import React, { useState } from 'react' import React, { useState } from 'react'
import { H4 } from 'src/components/typography' import { DialogTitle, ConfirmDialog } from 'src/components/ConfirmDialog'
import { Status } from 'src/components/Status'
import ActionButton from 'src/components/buttons/ActionButton'
import { Label1, H4 } from 'src/components/typography'
import { ReactComponent as RebootReversedIcon } from 'src/styling/icons/button/reboot/white.svg'
import { ReactComponent as RebootIcon } from 'src/styling/icons/button/reboot/zodiac.svg'
import { ReactComponent as UnpairReversedIcon } from 'src/styling/icons/button/unpair/white.svg'
import { ReactComponent as UnpairIcon } from 'src/styling/icons/button/unpair/zodiac.svg'
import { DialogTitle, ConfirmDialog } from '../../components/ConfirmDialog' import styles from './MachineDetailsCard.styles'
import { Status } from '../../components/Status'
import ActionButton from '../../components/buttons/ActionButton'
import { ReactComponent as DownloadReversedIcon } from '../../styling/icons/button/download/white.svg'
import { ReactComponent as DownloadIcon } from '../../styling/icons/button/download/zodiac.svg'
import { ReactComponent as RebootReversedIcon } from '../../styling/icons/button/reboot/white.svg'
import { ReactComponent as RebootIcon } from '../../styling/icons/button/reboot/zodiac.svg'
import { ReactComponent as ShutdownReversedIcon } from '../../styling/icons/button/shut down/white.svg'
import { ReactComponent as ShutdownIcon } from '../../styling/icons/button/shut down/zodiac.svg'
import { ReactComponent as UnpairReversedIcon } from '../../styling/icons/button/unpair/white.svg'
import { ReactComponent as UnpairIcon } from '../../styling/icons/button/unpair/zodiac.svg'
import { zircon } from '../../styling/variables'
import {
detailsRowStyles,
labelStyles
} from '../Transactions/Transactions.styles'
const MACHINE_ACTION = gql` const MACHINE_ACTION = gql`
mutation MachineAction($deviceId: ID!, $action: MachineAction!) { mutation MachineAction($deviceId: ID!, $action: MachineAction!) {
@ -33,30 +24,18 @@ const MACHINE_ACTION = gql`
} }
` `
const colDivider = { const useStyles = makeStyles(styles)
background: zircon,
width: 2
}
const inlineChip = {
marginInlineEnd: '0.25em'
}
const useLStyles = makeStyles(labelStyles)
const Label = ({ children }) => { const Label = ({ children }) => {
const classes = useLStyles() const classes = useStyles()
return <Label1 className={classes.label}>{children}</Label1>
return <div className={classes.label}>{children}</div>
} }
const useMDStyles = makeStyles({ ...detailsRowStyles, colDivider, inlineChip })
const MachineDetailsRow = ({ it: machine }) => { const MachineDetailsRow = ({ it: machine }) => {
const [errorDialog, setErrorDialog] = useState(false) const [errorDialog, setErrorDialog] = useState(false)
const [dialogOpen, setOpen] = useState(false) const [dialogOpen, setOpen] = useState(false)
const [actionMessage, setActionMessage] = useState(null) const [actionMessage, setActionMessage] = useState(null)
const classes = useMDStyles() const classes = useStyles()
const unpairDialog = () => setOpen(true) const unpairDialog = () => setOpen(true)
@ -79,157 +58,111 @@ const MachineDetailsRow = ({ it: machine }) => {
<DialogContent>{actionMessage}</DialogContent> <DialogContent>{actionMessage}</DialogContent>
</Dialog> </Dialog>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<div className={classnames(classes.row)}> <div className={classes.column1}>
<div className={classnames(classes.col)}> <div className={classes.lastRow}>
<div className={classnames(classes.row)}> <div className={classes.status}>
<div className={classnames(classes.col, classes.col2)}> <Label>Statuses</Label>
<div className={classes.innerRow}> <div>
<div> {machine.statuses.map((status, index) => (
<Label>Statuses</Label> <Status
<div> className={classes.chips}
{machine.statuses.map((status, index) => ( status={status}
<Status status={status} key={index} /> key={index}
))} />
</div> ))}
</div> </div>
<div> </div>
<Label>Lamassu Support article</Label> <div>
<div> <Label>Lamassu Support article</Label>
{machine.statuses.map((...[, index]) => ( <div>
<span key={index} /> {machine.statuses.map((...[, index]) => (
))} // TODO support articles
</div> <span key={index}></span>
</div> ))}
</div> </div>
</div>
<div className={classes.separator} />
</div>
</div>
<div className={classes.column2}>
<div className={classes.row}>
<div className={classes.machineModel}>
<Label>Machine Model</Label>
<div>{machine.model ?? 'unknown'}</div>
</div>
<div>
<Label>Paired at</Label>
<div>
{machine.pairedAt
? moment(machine.pairedAt).format('YYYY-MM-DD HH:mm:ss')
: 'N/A'}
</div> </div>
</div> </div>
</div> </div>
<div <div className={classes.lastRow}>
className={classnames( <div>
classes.col, <Label>Actions</Label>
classes.col2, <div className={classes.actionRow}>
classes.colDivider <ActionButton
)} className={classes.action}
/> color="primary"
<div className={classnames(classes.col)}> Icon={UnpairIcon}
<div className={classnames(classes.row)}> InverseIcon={UnpairReversedIcon}
<div className={classnames(classes.col, classes.col2)}> disabled={loading}
<div className={classes.innerRow}> onClick={unpairDialog}>
<div> Unpair
<Label>Machine Model</Label> </ActionButton>
<div>{machine.machineModel}</div> <ConfirmDialog
</div> open={dialogOpen}
<div className={classes.commissionWrapper}> className={classes.dialog}
<Label>Address</Label> title="Unpair this machine?"
<div>{machine.machineLocation}</div> subtitle={false}
</div> toBeConfirmed={machine.name}
</div> onConfirmed={() => {
</div> setOpen(false)
</div> machineAction({
variables: {
<div className={classnames(classes.row)}> deviceId: machine.deviceId,
<div className={classnames(classes.col, classes.col2)}> action: 'unpair'
<div className={classes.innerRow}> }
<div> })
<Label>Paired at</Label> }}
<div> onDissmised={() => {
{moment(machine.pairedAt).format('YYYY-MM-DD HH:mm:ss')} setOpen(false)
</div> }}
</div> />
<div className={classes.commissionWrapper}> <ActionButton
<Label>Software update</Label> className={classes.action}
<div className={classes.innerRow}> color="primary"
{machine.softwareVersion && ( Icon={RebootIcon}
<span className={classes.inlineChip}> InverseIcon={RebootReversedIcon}
{machine.softwareVersion} disabled={loading}
</span> onClick={() => {
)} machineAction({
<ActionButton variables: {
className={classes.inlineChip} deviceId: machine.deviceId,
disabled action: 'reboot'
color="primary" }
Icon={DownloadIcon} })
InverseIcon={DownloadReversedIcon}> }}>
Update Reboot
</ActionButton> </ActionButton>
</div> <ActionButton
</div> className={classes.action}
</div> disabled={loading}
</div> color="primary"
</div> Icon={RebootIcon}
InverseIcon={RebootReversedIcon}
<div className={classnames(classes.row)}> onClick={() => {
<div className={classnames(classes.col, classes.col2)}> machineAction({
<div className={classes.innerRow}> variables: {
<div> deviceId: machine.deviceId,
<Label>Printer</Label> action: 'restartServices'
<div>{machine.printer || 'unknown'}</div> }
</div> })
<div className={classes.commissionWrapper}> }}>
<Label>Actions</Label> Restart Services
<div className={classes.innerRow}> </ActionButton>
<ActionButton
className={classes.inlineChip}
color="primary"
Icon={UnpairIcon}
InverseIcon={UnpairReversedIcon}
disabled={loading}
onClick={unpairDialog}>
Unpair
</ActionButton>
<ConfirmDialog
open={dialogOpen}
title="Unpair this machine?"
subtitle={false}
toBeConfirmed={machine.name}
onConfirmed={() => {
setOpen(false)
machineAction({
variables: {
deviceId: machine.deviceId,
action: 'unpair'
}
})
}}
onDissmised={() => {
setOpen(false)
}}
/>
<ActionButton
className={classes.inlineChip}
color="primary"
Icon={RebootIcon}
InverseIcon={RebootReversedIcon}
disabled={loading}
onClick={() => {
machineAction({
variables: {
deviceId: machine.deviceId,
action: 'reboot'
}
})
}}>
Reboot
</ActionButton>
<ActionButton
className={classes.inlineChip}
disabled={loading}
color="primary"
Icon={ShutdownIcon}
InverseIcon={ShutdownReversedIcon}
onClick={() => {
machineAction({
variables: {
deviceId: machine.deviceId,
action: 'shutdown'
}
})
}}>
Shutdown
</ActionButton>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,60 @@
import { fade } from '@material-ui/core/styles/colorManipulator'
import { offColor, comet } from 'src/styling/variables'
export default {
wrapper: {
display: 'flex',
marginTop: 24,
marginBottom: 32,
fontSize: 14
},
column1: {
width: 600
},
column2: {
flex: 1
},
lastRow: {
display: 'flex',
flexDirection: 'row'
},
row: {
display: 'flex',
flexDirection: 'row',
marginBottom: 36
},
actionRow: {
display: 'flex',
flexDirection: 'row',
marginLeft: -4
},
action: {
marginRight: 4,
marginLeft: 4
},
dialog: {
width: 434
},
label: {
color: offColor,
margin: [[0, 0, 6, 0]]
},
chips: {
marginLeft: -2
},
status: {
width: 248
},
machineModel: {
width: 198
},
separator: {
width: 1,
height: 170,
zIndex: 1,
marginRight: 60,
marginLeft: 'auto',
background: fade(comet, 0.5)
}
}

View file

@ -1,6 +1,6 @@
import { useQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React from 'react' import React from 'react'
@ -20,6 +20,8 @@ const GET_MACHINES = gql`
machines { machines {
name name
deviceId deviceId
lastPing
pairedAt
paired paired
cashbox cashbox
cassette1 cassette1
@ -42,35 +44,28 @@ const MachineStatus = () => {
const elements = [ const elements = [
{ {
header: 'Machine Name', header: 'Machine Name',
width: 232, width: 250,
size: 'sm', size: 'sm',
textAlign: 'left', textAlign: 'left',
view: m => m.name view: m => m.name
}, },
{ {
header: 'Status', header: 'Status',
width: 349, width: 350,
size: 'sm', 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: 200,
size: 'sm', size: 'sm',
textAlign: 'left', textAlign: 'left',
view: m => moment(m.lastPing).fromNow() view: m => (m.lastPing ? moment(m.lastPing).fromNow() : 'unknown')
},
{
header: 'Ping Time',
width: 155,
size: 'sm',
textAlign: 'left',
view: m => m.pingTime || 'unknown'
}, },
{ {
header: 'Software Version', header: 'Software Version',
width: 201, width: 200,
size: 'sm', size: 'sm',
textAlign: 'left', textAlign: 'left',
view: m => m.softwareVersion || 'unknown' view: m => m.softwareVersion || 'unknown'

View file

@ -1,5 +1,5 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'

View file

@ -72,6 +72,7 @@ const FiatBalanceOverrides = ({ section }) => {
display: 'Cash-out 1', display: 'Cash-out 1',
width: 155, width: 155,
textAlign: 'right', textAlign: 'right',
doubleHeader: 'Cash-out (Cassette Empty)',
bold: true, bold: true,
input: TextInputFormik, input: TextInputFormik,
suffix: 'notes' suffix: 'notes'
@ -81,6 +82,7 @@ const FiatBalanceOverrides = ({ section }) => {
display: 'Cash-out 2', display: 'Cash-out 2',
width: 155, width: 155,
textAlign: 'right', textAlign: 'right',
doubleHeader: 'Cash-out (Cassette Empty)',
bold: true, bold: true,
input: TextInputFormik, input: TextInputFormik,
suffix: 'notes' suffix: 'notes'

View file

@ -1,6 +1,6 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState, memo } from 'react' import React, { useState, memo } from 'react'
@ -47,7 +47,6 @@ const CoinATMRadar = memo(() => {
const classes = useStyles() const classes = useStyles()
// TODO: treat errors on useMutation and useQuery
const [saveConfig] = useMutation(SAVE_CONFIG, { const [saveConfig] = useMutation(SAVE_CONFIG, {
onCompleted: configResponse => onCompleted: configResponse =>
setCoinAtmRadarConfig( setCoinAtmRadarConfig(

View file

@ -1,8 +1,8 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost'
import classnames from 'classnames' import classnames from 'classnames'
import { Form, Formik, Field as FormikField } from 'formik' import { Form, Formik, Field as FormikField } from 'formik'
import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -22,14 +22,14 @@ import {
} from './OperatorInfo.styles' } from './OperatorInfo.styles'
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
active: Yup.boolean().required(), active: Yup.boolean(),
name: Yup.string().required(), name: Yup.string(),
phone: Yup.string().required(), phone: Yup.string(),
email: Yup.string() email: Yup.string()
.email('Please enter a valid email address') .email('Please enter a valid email address')
.required(), .required(),
website: Yup.string().required(), website: Yup.string(),
companyNumber: Yup.string().required() companyNumber: Yup.string()
}) })
const fieldStyles = { const fieldStyles = {

View file

@ -16,6 +16,7 @@ const styles = {
height: '100%' height: '100%'
}, },
content: { content: {
flex: 1,
marginLeft: 48, marginLeft: 48,
paddingTop: 15 paddingTop: 15
} }

View file

@ -1,6 +1,6 @@
// import { makeStyles } from '@material-ui/core/styles' // import { makeStyles } from '@material-ui/core/styles'
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState, memo } from 'react' import React, { useState, memo } from 'react'
@ -49,10 +49,10 @@ const receiptPrintingOptions = [
code: 'off', code: 'off',
display: 'Off' display: 'Off'
}, },
{ // {
code: 'optional', // code: 'optional',
display: 'Optional (ask user)' // display: 'Optional (ask user)'
}, // },
{ {
code: 'on', code: 'on',
display: 'On' display: 'On'
@ -64,10 +64,8 @@ const ReceiptPrinting = memo(() => {
// const classes = useStyles() // const classes = useStyles()
// TODO: treat errors on useMutation and useQuery
const [saveConfig] = useMutation(SAVE_CONFIG, { const [saveConfig] = useMutation(SAVE_CONFIG, {
onCompleted: configResponse => { onCompleted: configResponse => {
console.log(configResponse.saveConfig)
return setReceiptPrintingConfig( return setReceiptPrintingConfig(
fromNamespace(namespaces.RECEIPT, configResponse.saveConfig) fromNamespace(namespaces.RECEIPT, configResponse.saveConfig)
) )
@ -87,7 +85,6 @@ const ReceiptPrinting = memo(() => {
}) })
if (!receiptPrintingConfig) return null if (!receiptPrintingConfig) return null
console.log(receiptPrintingConfig)
return ( return (
<> <>

View file

@ -1,8 +1,8 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost'
import classnames from 'classnames' import classnames from 'classnames'
import { Form, Formik, Field } from 'formik' import { Form, Formik, Field } from 'formik'
import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import * as Yup from 'yup' import * as Yup from 'yup'
@ -83,25 +83,30 @@ const TermsConditions = () => {
{ {
name: 'title', name: 'title',
label: 'Screen title', label: 'Screen title',
value: formData.title ?? '' value: formData.title ?? '',
width: 282
}, },
{ {
name: 'text', name: 'text',
label: 'Text content', label: 'Text content',
value: formData.text ?? '', value: formData.text ?? '',
multiline: true width: 502,
multiline: true,
rows: 6
}, },
{ {
name: 'acceptButtonText', name: 'acceptButtonText',
label: 'Accept button text', label: 'Accept button text',
value: formData.acceptButtonText ?? '', value: formData.acceptButtonText ?? '',
placeholder: 'I accept' placeholder: 'I accept',
width: 282
}, },
{ {
name: 'cancelButtonText', name: 'cancelButtonText',
label: 'Cancel button text', label: 'Cancel button text',
value: formData.cancelButtonText ?? '', value: formData.cancelButtonText ?? '',
placeholder: 'Cancel' placeholder: 'Cancel',
width: 282
} }
] ]
@ -145,10 +150,12 @@ const TermsConditions = () => {
id={f.name} id={f.name}
name={f.name} name={f.name}
component={TextInputFormik} component={TextInputFormik}
width={f.width}
placeholder={f.placeholder} placeholder={f.placeholder}
type="text" type="text"
label={f.label} label={f.label}
multiline={f.multiline} multiline={f.multiline}
rows={f.rows}
rowsMax="6" rowsMax="6"
onFocus={() => setError(null)} onFocus={() => setError(null)}
/> />

View file

@ -1,6 +1,6 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
@ -8,7 +8,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 Uptime from 'src/components/Uptime' import Uptime from 'src/components/Uptime'
import { FeatureButton, SimpleButton } from 'src/components/buttons' import { SimpleButton } from 'src/components/buttons'
import { Select } from 'src/components/inputs' import { Select } from 'src/components/inputs'
import { import {
Table, Table,
@ -20,8 +20,8 @@ import {
} from 'src/components/table' } from 'src/components/table'
import { Info3 } from 'src/components/typography' import { Info3 } from 'src/components/typography'
import typographyStyles from 'src/components/typography/styles' import typographyStyles from 'src/components/typography/styles'
import { ReactComponent as DownloadActive } from 'src/styling/icons/button/download/white.svg' import { ReactComponent as WhiteShareIcon } from 'src/styling/icons/circle buttons/share/white.svg'
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg' import { ReactComponent as ShareIcon } from 'src/styling/icons/circle buttons/share/zodiac.svg'
import { offColor } from 'src/styling/variables' import { offColor } from 'src/styling/variables'
import logsStyles from './Logs.styles' import logsStyles from './Logs.styles'
@ -41,7 +41,6 @@ const localStyles = {
margin: 'auto 0 auto 0' margin: 'auto 0 auto 0'
}, },
headerLine2: { headerLine2: {
height: 60,
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
marginBottom: 24 marginBottom: 24
@ -61,24 +60,14 @@ const formatDate = date => {
return moment(date).format('YYYY-MM-DD HH:mm') return moment(date).format('YYYY-MM-DD HH:mm')
} }
const GET_VERSION = gql` const GET_DATA = gql`
query {
serverVersion
}
`
const GET_UPTIME = gql`
{ {
serverVersion
uptime { uptime {
name name
state state
uptime uptime
} }
}
`
const GET_SERVER_LOGS = gql`
{
serverLogs { serverLogs {
logLevel logLevel
id id
@ -87,7 +76,6 @@ const GET_SERVER_LOGS = gql`
} }
} }
` `
const SUPPORT_LOGS = gql` const SUPPORT_LOGS = gql`
mutation ServerSupportLogs { mutation ServerSupportLogs {
serverSupportLogs { serverSupportLogs {
@ -101,30 +89,19 @@ const Logs = () => {
const [saveMessage, setSaveMessage] = useState(null) const [saveMessage, setSaveMessage] = useState(null)
const [logLevel, setLogLevel] = useState(SHOW_ALL) const [logLevel, setLogLevel] = useState(SHOW_ALL)
const [anchorEl, setAnchorEl] = useState(null)
const { data: version } = useQuery(GET_VERSION) const { data } = useQuery(GET_DATA, {
const serverVersion = version?.serverVersion
const { data: uptimeResponse } = useQuery(GET_UPTIME)
const processStates = uptimeResponse?.uptime ?? []
const { data: logsResponse } = useQuery(GET_SERVER_LOGS, {
fetchPolicy: 'no-cache',
onCompleted: () => setSaveMessage('') onCompleted: () => setSaveMessage('')
}) })
const serverVersion = data?.serverVersion
const processStates = data?.uptime ?? []
const [sendSnapshot, { loading }] = useMutation(SUPPORT_LOGS, { const [sendSnapshot, { loading }] = useMutation(SUPPORT_LOGS, {
onError: () => setSaveMessage('Failure saving snapshot'), onError: () => setSaveMessage('Failure saving snapshot'),
onCompleted: () => setSaveMessage('✓ Saved latest snapshot') onCompleted: () => setSaveMessage('✓ Saved latest snapshot')
}) })
const handleOpenRangePicker = event => {
setAnchorEl(anchorEl ? null : event.currentTarget)
}
const open = Boolean(anchorEl)
const id = open ? 'date-range-popover' : undefined
const getLogLevels = R.compose( const getLogLevels = R.compose(
R.prepend(SHOW_ALL), R.prepend(SHOW_ALL),
R.uniq, R.uniq,
@ -137,27 +114,19 @@ const Logs = () => {
<div className={classes.titleWrapper}> <div className={classes.titleWrapper}>
<div className={classes.titleAndButtonsContainer}> <div className={classes.titleAndButtonsContainer}>
<Title>Server</Title> <Title>Server</Title>
{logsResponse && ( {data && (
<div className={classes.buttonsWrapper}> <div className={classes.buttonsWrapper}>
<FeatureButton
Icon={Download}
InverseIcon={DownloadActive}
aria-describedby={id}
variant="contained"
onClick={handleOpenRangePicker}
/>
<LogsDowloaderPopover <LogsDowloaderPopover
title="Download logs" title="Download logs"
name="server-logs" name="server-logs"
id={id} logs={data.serverLogs}
open={open}
anchorEl={anchorEl}
logs={logsResponse.serverLogs}
getTimestamp={log => log.timestamp} getTimestamp={log => log.timestamp}
/> />
<SimpleButton <SimpleButton
className={classes.button} className={classes.shareButton}
disabled={loading} disabled={loading}
Icon={ShareIcon}
InverseIcon={WhiteShareIcon}
onClick={sendSnapshot}> onClick={sendSnapshot}>
Share with Lamassu Share with Lamassu
</SimpleButton> </SimpleButton>
@ -170,11 +139,11 @@ const Logs = () => {
</div> </div>
</div> </div>
<div className={classes.headerLine2}> <div className={classes.headerLine2}>
{logsResponse && ( {data && (
<Select <Select
onSelectedItemChange={setLogLevel} onSelectedItemChange={setLogLevel}
label="Level" label="Level"
items={getLogLevels(logsResponse)} items={getLogLevels(data)}
default={SHOW_ALL} default={SHOW_ALL}
selectedItem={logLevel} selectedItem={logLevel}
/> />
@ -197,8 +166,8 @@ const Logs = () => {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{logsResponse && {data &&
logsResponse.serverLogs data.serverLogs
.filter( .filter(
log => logLevel === SHOW_ALL || log.logLevel === logLevel log => logLevel === SHOW_ALL || log.logLevel === logLevel
) )

View file

@ -45,10 +45,11 @@ const FormRenderer = ({
onSubmit={save}> onSubmit={save}>
<Form className={classes.form}> <Form className={classes.form}>
<Grid container spacing={3} className={classes.grid}> <Grid container spacing={3} className={classes.grid}>
{elements.map(({ component, code, display }) => ( {elements.map(({ component, code, display, inputProps }) => (
<Grid item xs={12} key={code}> <Grid item xs={12} key={code}>
<FastField <FastField
component={component} component={component}
{...inputProps}
name={code} name={code}
label={display} label={display}
fullWidth={true} fullWidth={true}

View file

@ -1,6 +1,6 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles, Grid } from '@material-ui/core' import { makeStyles, Grid } from '@material-ui/core'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'

View file

@ -1,7 +1,10 @@
import * as Yup from 'yup' import * as Yup from 'yup'
import SecretInputFormik from 'src/components/inputs/formik/SecretInput' import {
import TextInputFormik from 'src/components/inputs/formik/TextInput' TextInput,
SecretInput,
Autocomplete
} from 'src/components/inputs/formik'
const isDefined = it => it && it.length const isDefined = it => it && it.length
@ -13,65 +16,71 @@ export default {
{ {
code: 'token', code: 'token',
display: 'API Token', display: 'API Token',
component: TextInputFormik, component: TextInput,
face: true, face: true,
long: true long: true
}, },
{ {
code: 'environment', code: 'environment',
display: 'Environment', display: 'Environment',
component: TextInputFormik, component: Autocomplete,
inputProps: {
options: ['prod', 'test']
// valueProp: 'deviceId',
// getLabel: R.path(['name']),
// limit: null
},
face: true face: true
}, },
{ {
code: 'btcWalletId', code: 'btcWalletId',
display: 'BTC Wallet ID', display: 'BTC Wallet ID',
component: TextInputFormik component: TextInput
}, },
{ {
code: 'btcWalletPassphrase', code: 'btcWalletPassphrase',
display: 'BTC Wallet Passphrase', display: 'BTC Wallet Passphrase',
component: SecretInputFormik component: SecretInput
}, },
{ {
code: 'ltcWalletId', code: 'ltcWalletId',
display: 'LTC Wallet ID', display: 'LTC Wallet ID',
component: TextInputFormik component: TextInput
}, },
{ {
code: 'ltcWalletPassphrase', code: 'ltcWalletPassphrase',
display: 'LTC Wallet Passphrase', display: 'LTC Wallet Passphrase',
component: SecretInputFormik component: SecretInput
}, },
{ {
code: 'zecWalletId', code: 'zecWalletId',
display: 'ZEC Wallet ID', display: 'ZEC Wallet ID',
component: TextInputFormik component: TextInput
}, },
{ {
code: 'zecWalletPassphrase', code: 'zecWalletPassphrase',
display: 'ZEC Wallet Passphrase', display: 'ZEC Wallet Passphrase',
component: SecretInputFormik component: SecretInput
}, },
{ {
code: 'bchWalletId', code: 'bchWalletId',
display: 'BCH Wallet ID', display: 'BCH Wallet ID',
component: TextInputFormik component: TextInput
}, },
{ {
code: 'bchWalletPassphrase', code: 'bchWalletPassphrase',
display: 'BCH Wallet Passphrase', display: 'BCH Wallet Passphrase',
component: SecretInputFormik component: SecretInput
}, },
{ {
code: 'dashWalletId', code: 'dashWalletId',
display: 'DASH Wallet ID', display: 'DASH Wallet ID',
component: TextInputFormik component: TextInput
}, },
{ {
code: 'dashWalletPassphrase', code: 'dashWalletPassphrase',
display: 'DASH Wallet Passphrase', display: 'DASH Wallet Passphrase',
component: SecretInputFormik component: SecretInput
} }
], ],
validationSchema: Yup.object().shape({ validationSchema: Yup.object().shape({

View file

@ -10,14 +10,14 @@ export default {
elements: [ elements: [
{ {
code: 'apiKey', code: 'apiKey',
display: 'API Key', display: 'Project ID',
component: TextInputFormik, component: TextInputFormik,
face: true, face: true,
long: true long: true
}, },
{ {
code: 'apiSecret', code: 'apiSecret',
display: 'API Secret', display: 'Project Secret',
component: SecretInputFormik component: SecretInputFormik
}, },
{ {

View file

@ -1,10 +1,10 @@
import { makeStyles } from '@material-ui/core/styles' import { makeStyles, Box } from '@material-ui/core'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import classnames from 'classnames'
import moment from 'moment' import moment from 'moment'
import React, { memo } from 'react' import React, { memo } from 'react'
import { IDButton } from 'src/components/buttons' import { IDButton } from 'src/components/buttons'
import { Label1 } from 'src/components/typography'
import { ReactComponent as CardIdInverseIcon } from 'src/styling/icons/ID/card/white.svg' import { ReactComponent as CardIdInverseIcon } from 'src/styling/icons/ID/card/white.svg'
import { ReactComponent as CardIdIcon } from 'src/styling/icons/ID/card/zodiac.svg' import { ReactComponent as CardIdIcon } from 'src/styling/icons/ID/card/zodiac.svg'
import { ReactComponent as PhoneIdInverseIcon } from 'src/styling/icons/ID/phone/white.svg' import { ReactComponent as PhoneIdInverseIcon } from 'src/styling/icons/ID/phone/white.svg'
@ -13,33 +13,55 @@ import { ReactComponent as CamIdInverseIcon } from 'src/styling/icons/ID/photo/w
import { ReactComponent as CamIdIcon } from 'src/styling/icons/ID/photo/zodiac.svg' import { ReactComponent as CamIdIcon } from 'src/styling/icons/ID/photo/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'
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg' import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
import { URI } from 'src/utils/apollo'
import { toUnit } from 'src/utils/coin' import { toUnit } from 'src/utils/coin'
import { onlyFirstToUpper } from 'src/utils/string' import { onlyFirstToUpper } from 'src/utils/string'
import CopyToClipboard from './CopyToClipboard' import CopyToClipboard from './CopyToClipboard'
import { detailsRowStyles, labelStyles } from './Transactions.styles' import styles from './DetailsCard.styles'
const labelUseStyles = makeStyles(labelStyles) const useStyles = makeStyles(styles)
const Label = ({ children }) => { const formatAddress = (address = '') => address.replace(/(.{5})/g, '$1 ')
const classes = labelUseStyles()
return <div className={classes.label}>{children}</div> const getCashOutStatus = it => {
if (it.hasError) return 'Error'
if (it.dispense) return 'Success'
if (it.expired) return 'Expired'
return 'Pending'
} }
const detailsUseStyles = makeStyles(detailsRowStyles) const getCashInStatus = it => {
console.log(it)
if (it.operatorCompleted) return 'Cancelled'
if (it.hasError) return 'Error'
if (it.sendConfirmed) return 'Sent'
if (it.expired) return 'Expired'
return 'Pending'
}
const DetailsRow = ({ it: tx, ...props }) => { const getStatus = it => {
const classes = detailsUseStyles() if (it.class === 'cashOut') {
return getCashOutStatus(it)
}
return getCashInStatus(it)
}
const Label = ({ children }) => {
const classes = useStyles()
return <Label1 className={classes.label}>{children}</Label1>
}
const DetailsRow = ({ it: tx }) => {
const classes = useStyles()
const addr = tx.toAddress
const txHash = tx.txHash
const fiat = Number.parseFloat(tx.fiat) const fiat = Number.parseFloat(tx.fiat)
const crypto = toUnit(new BigNumber(tx.cryptoAtoms), tx.cryptoCode).toFormat( const crypto = toUnit(new BigNumber(tx.cryptoAtoms), tx.cryptoCode)
5
)
const commissionPercentage = Number.parseFloat(tx.commissionPercentage, 2) const commissionPercentage = Number.parseFloat(tx.commissionPercentage, 2)
const commission = fiat * commissionPercentage const commission = fiat * commissionPercentage
const exchangeRate = Number(fiat / crypto).toFixed(3)
const displayExRate = `1 ${tx.cryptoCode} = ${exchangeRate} ${tx.fiatCode}`
const customer = tx.customerIdCardData && { const customer = tx.customerIdCardData && {
name: `${onlyFirstToUpper( name: `${onlyFirstToUpper(
tx.customerIdCardData.firstName tx.customerIdCardData.firstName
@ -52,170 +74,140 @@ const DetailsRow = ({ it: tx, ...props }) => {
) )
} }
const formatAddress = (address = '') => {
return address.replace(/(.{5})/g, '$1 ')
}
return ( return (
<> <div className={classes.wrapper}>
<div className={classes.wrapper}> <div className={classes.row}>
<div className={classnames(classes.row)}> <div className={classes.direction}>
<div className={classnames(classes.col, classes.col1)}> <Label>Direction</Label>
{/* Column 1 */} <div>
<div className={classes.innerRow}> <span className={classes.txIcon}>
<div> {tx.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />}
<Label>Direction</Label> </span>
<div> <span>{tx.txClass === 'cashOut' ? 'Cash-out' : 'Cash-in'}</span>
<span className={classes.txIcon}>
{tx.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />}
</span>
<span>
{tx.txClass === 'cashOut' ? 'Cash-out' : 'Cash-in'}
</span>
</div>
</div>
<div className={classes.availableIds}>
<Label>Available IDs</Label>
<div>
{tx.customerPhone && (
<IDButton
name="phone"
Icon={PhoneIdIcon}
InverseIcon={PhoneIdInverseIcon}>
{tx.customerPhone}
</IDButton>
)}
{tx.customerIdCardPhotoPath && !tx.customerIdCardData && (
<IDButton
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<img alt="" src={tx.customerIdCardPhotoPath} />
</IDButton>
)}
{tx.customerIdCardData && (
<IDButton
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<div className={classes.idCardDataCard}>
<div>
<div>
<Label>Name</Label>
<div>{customer.name}</div>
</div>
<div>
<Label>Age</Label>
<div>{customer.age}</div>
</div>
<div>
<Label>Country</Label>
<div>{customer.country}</div>
</div>
</div>
<div>
<div>
<Label>ID number</Label>
<div>{customer.idCardNumber}</div>
</div>
<div>
<Label>Gender</Label>
<div />
</div>
<div>
<Label>Expiration date</Label>
<div>{customer.idCardExpirationDate}</div>
</div>
</div>
</div>
</IDButton>
)}
{tx.customerFrontCameraPath && (
<IDButton
name="cam"
Icon={CamIdIcon}
InverseIcon={CamIdInverseIcon}>
<img alt="" src={tx.customerFrontCameraPath} />
</IDButton>
)}
</div>
</div>
</div>
</div>
<div className={classnames(classes.col, classes.col2)}>
{/* Column 2 */}
<div className={classes.innerRow}>
<div>
<Label>Exchange rate</Label>
<div>
{`1 ${tx.cryptoCode} = ${Number(fiat / crypto).toFixed(3)} ${
tx.fiatCode
}`}
</div>
</div>
<div className={classes.commissionWrapper}>
<Label>Commission</Label>
<div>
{`${commission} ${tx.fiatCode} (${commissionPercentage *
100} %)`}
</div>
</div>
{tx.txClass === 'cashIn' && (
<div className={classes.innerRow}>
<Label>Fixed fee</Label>
<div>{Number.parseFloat(tx.cashInFee)}</div>
</div>
)}
</div>
</div>
<div className={classnames(classes.col, classes.col3)}>
{/* Column 3 */}
<div className={classnames(classes.innerRow)}>
<div style={{ height: 43.4 }}>{/* Export to PDF */}</div>
</div>
</div> </div>
</div> </div>
<div className={classnames(classes.row)}>
<div className={classnames(classes.col, classes.col1)}> <div className={classes.availableIds}>
{/* Column 1 */} <Label>Available IDs</Label>
<div className={classes.innerRow}> <Box display="flex" flexDirection="row">
<div> {tx.customerPhone && (
<Label>BTC address</Label> <IDButton
<div> className={classes.idButton}
<CopyToClipboard className={classes.cryptoAddr}> name="phone"
{formatAddress(addr)} Icon={PhoneIdIcon}
</CopyToClipboard> InverseIcon={PhoneIdInverseIcon}>
{tx.customerPhone}
</IDButton>
)}
{tx.customerIdCardPhotoPath && !tx.customerIdCardData && (
<IDButton
popoverClassname={classes.popover}
className={classes.idButton}
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<img
className={classes.idCardPhoto}
src={`${URI}/id-card-photo/${tx.customerIdCardPhotoPath}`}
alt=""
/>
</IDButton>
)}
{tx.customerIdCardData && (
<IDButton
className={classes.idButton}
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<div className={classes.idCardDataCard}>
<div>
<div>
<Label>Name</Label>
<div>{customer.name}</div>
</div>
<div>
<Label>Age</Label>
<div>{customer.age}</div>
</div>
<div>
<Label>Country</Label>
<div>{customer.country}</div>
</div>
</div>
<div>
<div>
<Label>ID number</Label>
<div>{customer.idCardNumber}</div>
</div>
<div>
<Label>Expiration date</Label>
<div>{customer.idCardExpirationDate}</div>
</div>
</div>
</div> </div>
</div> </IDButton>
</div> )}
{tx.customerFrontCameraPath && (
<IDButton
name="cam"
Icon={CamIdIcon}
InverseIcon={CamIdInverseIcon}>
<img
src={`${URI}/front-camera-photo/${tx.customerFrontCameraPath}`}
alt=""
/>
</IDButton>
)}
</Box>
</div>
<div className={classes.exchangeRate}>
<Label>Exchange rate</Label>
<div>{crypto > 0 ? displayExRate : '-'}</div>
</div>
<div className={classes.commission}>
<Label>Commission</Label>
<div>
{`${commission} ${tx.fiatCode} (${commissionPercentage * 100} %)`}
</div> </div>
<div className={classnames(classes.col, classes.col2)}> </div>
{/* Column 2 */} <div>
<div className={classes.innerRow}> <Label>Fixed fee</Label>
<div> <div>
<Label>Transaction ID</Label> {tx.txClass === 'cashIn'
<div> ? `${Number.parseFloat(tx.cashInFee)} ${tx.fiatCode}`
<CopyToClipboard className={classes.txId}> : 'N/A'}
{txHash}
</CopyToClipboard>
</div>
</div>
</div>
</div>
<div className={classnames(classes.col, classes.col3)}>
{/* Column 3 */}
<div className={classes.innerRow}>
<div>
<Label>Session ID</Label>
<CopyToClipboard className={classes.sessionId}>
{tx.id}
</CopyToClipboard>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</> <div className={classes.secondRow}>
<div className={classes.address}>
<Label>BTC address</Label>
<div>
<CopyToClipboard>{formatAddress(tx.toAddress)}</CopyToClipboard>
</div>
</div>
<div className={classes.transactionId}>
<Label>Transaction ID</Label>
<div>
{tx.txClass === 'cashOut' ? (
'N/A'
) : (
<CopyToClipboard>{tx.txHash}</CopyToClipboard>
)}
</div>
</div>
<div className={classes.sessionId}>
<Label>Session ID</Label>
<CopyToClipboard>{tx.id}</CopyToClipboard>
</div>
</div>
<div className={classes.lastRow}>
<div>
<Label>Transaction status</Label>
<span className={classes.bold}>{getStatus(tx)}</span>
</div>
</div>
</div>
) )
} }

View file

@ -0,0 +1,84 @@
import typographyStyles from 'src/components/typography/styles'
import { offColor } from 'src/styling/variables'
const { p } = typographyStyles
export default {
wrapper: {
display: 'flex',
flexDirection: 'column',
marginTop: 24
},
row: {
display: 'flex',
flexDirection: 'row',
marginBottom: 36
},
secondRow: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 36
},
lastRow: {
display: 'flex',
flexDirection: 'row',
marginBottom: 32
},
label: {
color: offColor,
margin: [[0, 0, 6, 0]]
},
txIcon: {
marginRight: 10
},
popover: {
height: 164,
width: 215
},
idButton: {
marginRight: 4
},
idCardDataCard: {
extend: p,
display: 'flex',
padding: [[11, 8]],
// rework this into a proper component
'& > div': {
display: 'flex',
flexDirection: 'column',
'& > div': {
width: 144,
height: 37,
marginBottom: 15,
'&:last-child': {
marginBottom: 0
}
}
}
},
bold: {
fontWeight: 700
},
direction: {
width: 233
},
availableIds: {
width: 232
},
exchangeRate: {
width: 250
},
commission: {
width: 217
},
address: {
width: 280
},
transactionId: {
width: 280
},
sessionId: {
width: 215
}
}

View file

@ -1,17 +1,14 @@
import { useQuery } from '@apollo/react-hooks' import { useQuery } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import gql from 'graphql-tag'
import moment from 'moment' import moment from 'moment'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React 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 DataTable from 'src/components/tables/DataTable' import DataTable from 'src/components/tables/DataTable'
import { ReactComponent as DownloadInverseIcon } from 'src/styling/icons/button/download/white.svg'
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg'
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg' 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'
import { toUnit } from 'src/utils/coin' import { toUnit } from 'src/utils/coin'
@ -21,7 +18,6 @@ import { mainStyles } from './Transactions.styles'
const useStyles = makeStyles(mainStyles) const useStyles = makeStyles(mainStyles)
// TODO customerIdCardData
const GET_TRANSACTIONS = gql` const GET_TRANSACTIONS = gql`
{ {
transactions { transactions {
@ -30,7 +26,12 @@ const GET_TRANSACTIONS = gql`
txHash txHash
toAddress toAddress
commissionPercentage commissionPercentage
expired
machineName machineName
operatorCompleted
sendConfirmed
dispense
hasError: error
deviceId deviceId
fiat fiat
cashInFee cashInFee
@ -49,8 +50,6 @@ const GET_TRANSACTIONS = gql`
` `
const Transactions = () => { const Transactions = () => {
const [anchorEl, setAnchorEl] = useState(null)
const classes = useStyles() const classes = useStyles()
const { data: txResponse } = useQuery(GET_TRANSACTIONS) const { data: txResponse } = useQuery(GET_TRANSACTIONS)
@ -127,17 +126,6 @@ const Transactions = () => {
} }
] ]
const handleOpenRangePicker = event => {
setAnchorEl(anchorEl ? null : event.currentTarget)
}
const handleCloseRangePicker = () => {
setAnchorEl(null)
}
const open = Boolean(anchorEl)
const id = open ? 'date-range-popover' : undefined
return ( return (
<> <>
<div className={classes.titleWrapper}> <div className={classes.titleWrapper}>
@ -145,22 +133,11 @@ const Transactions = () => {
<Title>Transactions</Title> <Title>Transactions</Title>
{txResponse && ( {txResponse && (
<div className={classes.buttonsWrapper}> <div className={classes.buttonsWrapper}>
<FeatureButton
Icon={Download}
InverseIcon={DownloadInverseIcon}
aria-describedby={id}
variant="contained"
onClick={handleOpenRangePicker}
/>
<LogsDowloaderPopover <LogsDowloaderPopover
title="Download logs" title="Download logs"
name="transactions" name="transactions"
id={id}
open={open}
anchorEl={anchorEl}
logs={txResponse.transactions} logs={txResponse.transactions}
getTimestamp={tx => tx.created} getTimestamp={tx => tx.created}
onClose={handleCloseRangePicker}
/> />
</div> </div>
)} )}

View file

@ -1,6 +1,6 @@
import typographyStyles from 'src/components/typography/styles' import typographyStyles from 'src/components/typography/styles'
import { offColor, white } from 'src/styling/variables'
import baseStyles from 'src/pages/Logs.styles' import baseStyles from 'src/pages/Logs.styles'
import { offColor, white } from 'src/styling/variables'
const { label1, mono, p } = typographyStyles const { label1, mono, p } = typographyStyles
const { titleWrapper, titleAndButtonsContainer, buttonsWrapper } = baseStyles const { titleWrapper, titleAndButtonsContainer, buttonsWrapper } = baseStyles
@ -34,66 +34,6 @@ const cpcStyles = {
} }
const detailsRowStyles = { const detailsRowStyles = {
wrapper: {
display: 'flex',
flexDirection: 'column'
},
col: {
display: 'flex',
flexDirection: 'column'
},
col1: {
width: 413
},
col2: {
width: 506
},
col3: {
width: 233
},
innerRow: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between'
},
row: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
margin: [[25, 0]]
},
mono: {
extend: mono
},
txIcon: {
marginRight: 10
},
availableIds: {
width: 110,
marginRight: 61,
'& > div': {
display: 'flex',
flexDirection: 'row',
'& button': {
'&:first-child': {
marginRight: 4
},
'&:last-child': {
marginLeft: 4
},
'&:only-child': {
margin: 0
},
'&:nth-child(2):last-child': {
margin: 0
}
}
}
},
commissionWrapper: {
width: 110,
marginRight: 155
},
idCardDataCard: { idCardDataCard: {
extend: p, extend: p,
display: 'flex', display: 'flex',
@ -110,15 +50,6 @@ const detailsRowStyles = {
} }
} }
} }
},
cryptoAddr: {
width: 252
},
txId: {
width: 346
},
sessionId: {
width: 184
} }
} }

View file

@ -1,15 +1,13 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { v4 } from 'uuid' import { v4 } from 'uuid'
import Title from 'src/components/Title' import Title from 'src/components/Title'
import { FeatureButton, Link } from 'src/components/buttons' import { Link } from 'src/components/buttons'
import { Table as EditableTable } from 'src/components/editableTable' import { Table as EditableTable } from 'src/components/editableTable'
import { ReactComponent as ConfigureInverseIcon } from 'src/styling/icons/button/configure/white.svg'
import { ReactComponent as Configure } from 'src/styling/icons/button/configure/zodiac.svg'
import { mainStyles } from './Triggers.styles' import { mainStyles } from './Triggers.styles'
import Wizard from './Wizard' import Wizard from './Wizard'
@ -60,13 +58,6 @@ const Triggers = () => {
<div className={classes.titleWrapper}> <div className={classes.titleWrapper}>
<div className={classes.titleAndButtonsContainer}> <div className={classes.titleAndButtonsContainer}>
<Title>Compliance Triggers</Title> <Title>Compliance Triggers</Title>
<div className={classes.buttonsWrapper}>
<FeatureButton
Icon={Configure}
InverseIcon={ConfigureInverseIcon}
variant="contained"
/>
</div>
</div> </div>
<div className={classes.headerLabels}> <div className={classes.headerLabels}>
<Link color="primary" onClick={() => setWizard(true)}> <Link color="primary" onClick={() => setWizard(true)}>

View file

@ -1,5 +1,5 @@
import { useQuery, useMutation } from '@apollo/react-hooks' import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'

View file

@ -36,10 +36,10 @@ const tree = [
}, },
children: [ children: [
{ {
key: 'logs', key: 'cashboxes',
label: 'Logs', label: 'Cashboxes',
route: '/maintenance/logs', route: '/maintenance/cashboxes',
component: MachineLogs component: Cashboxes
}, },
{ {
key: 'funding', key: 'funding',
@ -48,10 +48,10 @@ const tree = [
component: Funding component: Funding
}, },
{ {
key: 'server-logs', key: 'logs',
label: 'Server', label: 'Machine logs',
route: '/maintenance/server-logs', route: '/maintenance/logs',
component: ServerLogs component: MachineLogs
}, },
{ {
key: 'machine-status', key: 'machine-status',
@ -60,10 +60,10 @@ const tree = [
component: MachineStatus component: MachineStatus
}, },
{ {
key: 'cashboxes', key: 'server-logs',
label: 'Cashboxes', label: 'Server',
route: '/maintenance/cashboxes', route: '/maintenance/server-logs',
component: Cashboxes component: ServerLogs
} }
] ]
}, },
@ -83,7 +83,7 @@ const tree = [
}, },
{ {
key: namespaces.LOCALE, key: namespaces.LOCALE,
label: 'Locale', label: 'Locales',
route: '/settings/locale', route: '/settings/locale',
component: Locales component: Locales
}, },
@ -93,12 +93,6 @@ const tree = [
route: '/settings/cash-out', route: '/settings/cash-out',
component: Cashout component: Cashout
}, },
{
key: 'services',
label: '3rd party services',
route: '/settings/3rd-party-services',
component: Services
},
{ {
key: namespaces.NOTIFICATIONS, key: namespaces.NOTIFICATIONS,
label: 'Notifications', label: 'Notifications',
@ -106,16 +100,22 @@ const tree = [
component: Notifications component: Notifications
}, },
{ {
key: namespaces.OPERATOR_INFO, key: 'services',
label: 'Operator Info', label: '3rd party services',
route: '/settings/operator-info', route: '/settings/3rd-party-services',
component: OperatorInfo component: Services
}, },
{ {
key: namespaces.WALLETS, key: namespaces.WALLETS,
label: 'Wallet', label: 'Wallet',
route: '/settings/wallet-settings', route: '/settings/wallet-settings',
component: WalletSettings component: WalletSettings
},
{
key: namespaces.OPERATOR_INFO,
label: 'Operator Info',
route: '/settings/operator-info',
component: OperatorInfo
} }
] ]
}, },

View file

@ -4,14 +4,14 @@
<title>icon/action/arrow/regular</title> <title>icon/action/arrow/regular</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<defs> <defs>
<path d="M5.3501239,7.53208616 L0.473798314,2.73082122 C-0.158421727,2.1051411 -0.158421727,1.0952488 0.476737158,0.466675069 C1.11220338,-0.155816755 2.1378971,-0.155816755 2.77494316,0.468226909 L6.49990857,4.13723769 L10.2264532,0.466675069 C10.8619195,-0.155816755 11.8876132,-0.155816755 12.5260183,0.469568675 C13.1582383,1.0952488 13.1582383,2.1051411 12.5245507,2.73226987 L7.64673876,7.53497972 C7.33802629,7.83583835 6.92590837,8 6.49990828,8 C6.0739082,8 5.66179027,7.83583835 5.3501239,7.53208616 Z" id="path-1"></path> <path id="arrow-path" d="M5.3501239,7.53208616 L0.473798314,2.73082122 C-0.158421727,2.1051411 -0.158421727,1.0952488 0.476737158,0.466675069 C1.11220338,-0.155816755 2.1378971,-0.155816755 2.77494316,0.468226909 L6.49990857,4.13723769 L10.2264532,0.466675069 C10.8619195,-0.155816755 11.8876132,-0.155816755 12.5260183,0.469568675 C13.1582383,1.0952488 13.1582383,2.1051411 12.5245507,2.73226987 L7.64673876,7.53497972 C7.33802629,7.83583835 6.92590837,8 6.49990828,8 C6.0739082,8 5.66179027,7.83583835 5.3501239,7.53208616 Z"></path>
</defs> </defs>
<g id="Styleguide" stroke="none" stroke-width="1" fill-rule="evenodd"> <g id="Styleguide" stroke="none" stroke-width="1" fill-rule="evenodd">
<g id="icon/action/arrow/regular"> <g id="icon/action/arrow/regular">
<mask id="mask-2" fill="white"> <mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use> <use xlink:href="#path-1"></use>
</mask> </mask>
<use id="Mask" fill-rule="nonzero" xlink:href="#path-1"></use> <use id="Mask" fill-rule="nonzero" xlink:href="#arrow-path"></use>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before After
Before After

View file

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <svg
<svg width="13px" height="8px" viewBox="0 0 13 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> width="13"
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com --> height="8"
<title>icon/action/arrow/regular/zodiac</title> viewBox="0 0 13 8"
<desc>Created with Sketch.</desc> xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<path d="M5.3501239,7.53208616 L0.473798314,2.73082122 C-0.158421727,2.1051411 -0.158421727,1.0952488 0.476737158,0.466675069 C1.11220338,-0.155816755 2.1378971,-0.155816755 2.77494316,0.468226909 L6.49990857,4.13723769 L10.2264532,0.466675069 C10.8619195,-0.155816755 11.8876132,-0.155816755 12.5260183,0.469568675 C13.1582383,1.0952488 13.1582383,2.1051411 12.5245507,2.73226987 L7.64673876,7.53497972 C7.33802629,7.83583835 6.92590837,8 6.49990828,8 C6.0739082,8 5.66179027,7.83583835 5.3501239,7.53208616 Z" id="path-1"></path> <path id="path-2" d="M5.4 7.5L0.5 2.7C-0.2 2.1-0.2 1.1 0.5 0.5 1.1-0.2 2.1-0.2 2.8 0.5L6.5 4.1 10.2 0.5C10.9-0.2 11.9-0.2 12.5 0.5 13.2 1.1 13.2 2.1 12.5 2.7L7.6 7.5C7.3 7.8 6.9 8 6.5 8 6.1 8 5.7 7.8 5.4 7.5Z" />
</defs> </defs>
<g id="icon/action/arrow/regular/zodiac" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g fill="none">
<mask id="mask-2" fill="white"> <mask fill="white">
<use xlink:href="#path-1"></use> <use xlink:href="#path-1" />
</mask> </mask>
<use id="Mask" fill="#1B2559" fill-rule="nonzero" xlink:href="#path-1"></use> <use xlink:href="#path-1" fill="#1B2559" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 458 B

Before After
Before After

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>icon/sf-small/share/zodiac</title>
<desc>Created with Sketch.</desc>
<g id="icon/sf-small/share/zodiac" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
<g id="share" transform="translate(4.000000, 4.000000)" stroke="white">
<polygon id="Combined-Shape" points="7.57894737 12 12 0 0 3.52941176 5.05263158 6.35294118"></polygon>
<path d="M5.05263158,6.35294118 L12,-1.60760294e-13 L5.05263158,6.35294118 Z" id="Stroke-3"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 818 B

View file

@ -0,0 +1,43 @@
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloClient } from 'apollo-client'
import { ApolloLink } from 'apollo-link'
import { onError } from 'apollo-link-error'
import { HttpLink } from 'apollo-link-http'
const URI =
process.env.NODE_ENV === 'development' ? 'https://localhost:8070' : ''
const client = new ApolloClient({
link: ApolloLink.from([
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
)
if (networkError) console.log(`[Network error]: ${networkError}`)
}),
new HttpLink({
credentials: 'include',
uri: `${URI}/graphql`
})
]),
cache: new InMemoryCache(),
defaultOptions: {
watchQuery: {
fetchPolicy: 'no-cache',
errorPolicy: 'ignore'
},
query: {
fetchPolicy: 'no-cache',
errorPolicy: 'all'
},
mutate: {
errorPolicy: 'all'
}
}
})
export default client
export { URI }

View file

@ -1,28 +1,74 @@
UI: Main menu:
- Large input fields wiggle on editable table edit/non-edit mode change. - do not change fonts on hover in the main menu
- all (machines/coins/...) should be a option on some overrides - make the clickable button bigger, not just text
Compliance: Overall:
- Reject Address Reuse missing - caching the page
- coin dropdown should show all coins
- validation is bad rn, negatives being allowed
- input number should only allow numbers
- right aligned numbers on all tables
- locale based mil separators 1.000 1,000
Cashboxes:
- right aligned number (SAME EVERYWHERE)
UI:
- replace all the tooltips with new component
- tooltip like components should close on esc
- saving should be a one time thing. disable buttons so user doesnt spam it
- transitions
- error handling
- should all (machines/coins/...) be a option on some overrides?
- select components
- talk with nunu + neal: Hover css for edit buttons + first first cancel later
- filter countries by code as well, US should go to United States
- filter prioritize the start of words(not alphabetically)
- dropdown should have everythihg selected on the top
- disable edit on non-everrides => overrides
- remove the broswer default tooltip
Machine status:
- legend colors are different from the spec
- action Error/Success indication
- load machine model from l-m
- align popup title with content
- talk with neal to see if the actions should be consistent
- font-size of the 'write to confirm'
- reboot icon cut off
- ask neal for the support articles
- stop line breaking on multi select
Commissions:
- overrides can be tighter. Hide coins already used by the same machine on another line.
- no negative values
- autoselect not getting errored when tabbed out
Operator Info:
- That should be paginated with routes!
Terms and Conditions:
- default values are not working properly
Contact information:
- When the fields are empty, should there be a warning somewhere? Or maybe we could create an exception that if the fields are empty they shouldn't show up
- l-m uses name, email, phone. The rest is just used for the receipt printing for now
CoinATMRadar: CoinATMRadar:
- We now have photo, should we relay that info? - We now have photo, should we relay that info?
Locale:
- should we show the user wallet needs to be configured after adding in a new coin?
- check if coin is active before considering it active on other screens
Commission:
- overrides can be tighter. Hide coins already used by the same machine on another line.
Sms/email: Sms/email:
- There's no place to pick a third party provider anymore. - There's no place to pick a third party provider anymore. (sms.js, email.js)
Cashout:
- I've just added a zero conf limit column. Should it be like this?
Notifications: Notifications:
- cashInAlertThreshold missing, used to warn to full cashbox - cash out 500 notes max top 500 max bottom
- crypto balance alerts input width (CHECK FOR ALL)
Locale:
- limit languages
- search crypto per name as well
- show full name on the dropdown
Machine name: Machine name:
- Previously we were grabbing that from the config, but since new admin still cant change the name i`m now grabbing it from the db. Possible issues if users change the machine name from the initial one. Investivate alternatives. - Previously we were grabbing that from the config, but since new admin still cant change the name i`m now grabbing it from the db. Possible issues if users change the machine name from the initial one. Investivate alternatives.
@ -33,9 +79,32 @@ Migrate:
- remove apply defaults - remove apply defaults
Compliance: Compliance:
- Reject Address Reuse missing
- Currently admin only handles { type: 'volume', direction: 'both' } - Currently admin only handles { type: 'volume', direction: 'both' }
- Sanctions should have more care in customers.js, currently just looking if is active as if old config - Sanctions should have more care in customers.js, currently just looking if is active as if old config
Other stuff: Customers:
- sms.js - Should add id and make it main part of the table? Name is not common at all
- email.js
Logs:
- the new functionality that saves server logs to a db breaks initial install chicken-egg with db-logger
Downloading (logs and tx):
- They are always downloading from the local data, should be from server
Cash out:
- On off should have a fixed sized so things dont move a lot
- separate text from the first screen
- auto focus on fields after clicking next
- improve spacing around paragraphs
- button is on a wrong place on steps 2 and 3
- make it a dropdown based on the machine denomimnations settings
- ask nuno about zero conf limit
- USD should show as a suffix (validate all screens)
- Splash image for wizard
Server:
- Takes too long to load. Investigate
Review slow internet loading:
- Table should be loaded