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"
]
},
{
"code": "walletSettings",
"fields": [
"ticker",
"wallet",
"layer2",
"exchange",
"zeroConf"
]
},
{
"code": "notifications",
"fields": [

View file

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

View file

@ -32,6 +32,7 @@ function mapCoin (rates, deviceId, settings, cryptoCode) {
const cashInFee = showCommissions ? commissions.cashIn / 100 : null
const cashOutFee = showCommissions ? commissions.cashOut / 100 : null
const cashInFixedFee = showCommissions ? commissions.fixedFee : null
const cashInRate = showCommissions ? _.invoke('cashIn.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
// need to get in touch with coinatmradar before updating this
// TODO all directions and max between them instead of min
const cashLimit = showLimitsAndVerification ? (
!!compatTriggers.block
? compatTriggers.block

View file

@ -1,7 +1,7 @@
const _ = require('lodash/fp')
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)
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,
count(0) over (partition by c.id) as total_txs,
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
from cash_in_txs where send_confirmed = true union
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.query) {
logger.error(e.query)
logger.error(e.params)
e.params && logger.error(e.params)
}
logger.error(err)
}
@ -21,11 +21,12 @@ const db = pgp(psqlUrl)
eventBus.subscribe('log', args => {
const { level, message, meta } = args
const msgToSave = message ? message : _.get('message', meta)
const sql = `insert into server_logs
(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))
})

View file

@ -1,6 +1,7 @@
const _ = require('lodash/fp')
const axios = require('axios')
const logger = require('./logger')
const db = require('./db')
const pairing = require('./pairing')
const configManager = require('./new-config-manager')
@ -15,8 +16,10 @@ function getMachines () {
cashbox: r.cashbox,
cassette1: r.cassette1,
cassette2: r.cassette2,
pairedAt: new Date(r.created).valueOf(),
lastPing: new Date(r.last_online).valueOf(),
version: r.version,
model: r.model,
pairedAt: new Date(r.created),
lastPing: new Date(r.last_online),
name: r.name,
// TODO: we shall start using this JSON field at some point
// location: r.location,
@ -36,19 +39,12 @@ function getMachineNames (config) {
const addName = r => {
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
const machineModel = ''
const machineLocation = ''
// TODO: obtain next fields from somewhere
const printer = null
const pingTime = null
// TODO new-admin actually load status based on ping.
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)

View file

@ -1,3 +1,4 @@
const _ = require('lodash/fp')
const { COINS, ALL_CRYPTOS } = require('./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 ticker = require('../ticker')
const coinUtils = require('../coin-utils')
const logger = require('../logger')
function allScopes (cryptoScopes, machineScopes) {
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 () {
return settingsLoader.loadLatest().then(settings => {
const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config)
@ -77,9 +81,10 @@ function getFunding () {
const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies)
const promises = cryptoDisplays.map(it => getSingleCoinFunding(settings, fiatCode, it.cryptoCode))
return Promise.all(promises)
return Promise.all(promises.map(reflect))
.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 { accounts: accountsConfig, coins, countries, currencies, languages } = require('../config')
// TODO why does server logs messages can be null?
const typeDefs = gql`
scalar JSON
scalar JSONObject
@ -54,6 +53,10 @@ const typeDefs = gql`
name: String!
deviceId: ID!
paired: Boolean!
lastPing: Date
pairedAt: Date
version: String
model: String
cashbox: Int
cassette1: Int
cassette2: Int
@ -123,21 +126,22 @@ const typeDefs = gql`
type CoinFunds {
cryptoCode: String!
fundingAddress: String!
fundingAddressUrl: String!
confirmedBalance: String!
pending: String!
fiatConfirmedBalance: String!
fiatPending: String!
fiatCode: String!
display: String!
unitScale: String!
errorMsg: String
fundingAddress: String
fundingAddressUrl: String
confirmedBalance: String
pending: String
fiatConfirmedBalance: String
fiatPending: String
fiatCode: String
display: String
unitScale: String
}
type ProcessStatus {
name: String!
state: String!
uptime: Date!
uptime: Int!
}
type Transaction {
@ -156,6 +160,7 @@ const typeDefs = gql`
created: Date
send: Boolean
sendConfirmed: Boolean
dispense: Boolean
timedout: Boolean
sendTime: Date
errorCode: String

View file

@ -2,6 +2,10 @@ const xmlrpc = require('xmlrpc')
const logger = require('../logger')
const { promisify } = require('util')
// TODO new-admin: add the following to supervisor config
// [inet_http_server]
// port = 127.0.0.1:9001
function getAllProcessInfo () {
const convertStates = (state) => {
// From http://supervisord.org/subprocess.html#process-states
@ -45,7 +49,7 @@ function getAllProcessInfo () {
{
name: process.name,
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')
let db = null
// TODO new-admin save to actual db, like the old settings
low(adapter).then(it => {
db = it
})
@ -54,6 +55,7 @@ function loadLatest () {
})
}
// TODO new-admin: grab correct version
function load (versionId) {
return new Promise((resolve) => {
if (!db) {

View file

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

View file

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

View file

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

View file

@ -122,7 +122,7 @@ function mergeStatusMode (a, b) {
}
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 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": {
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz",
"integrity": "sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA==",
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.6.tgz",
"integrity": "sha512-L8pToTW/+Xru2FFAhkZ1OA9q4V4nuvfoPecBM34DecAugUZEBhI2Hmpgnzq2hTKZ60LAMrlqiASm0aqAY6F8/A==",
"requires": {
"apollo-cache": "^1.3.4",
"apollo-utilities": "^1.3.3",
"apollo-cache": "^1.3.5",
"apollo-utilities": "^1.3.4",
"optimism": "^0.10.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0"
},
"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": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
}
}
},
"apollo-client": {
"version": "2.6.8",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.8.tgz",
"integrity": "sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw==",
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.6.10.tgz",
"integrity": "sha512-jiPlMTN6/5CjZpJOkGeUV0mb4zxx33uXWdj/xQCfAMkuNAC3HN7CvYDyMHHEzmcQ5GV12LszWoQ/VlxET24CtA==",
"requires": {
"@types/zen-observable": "^0.8.0",
"apollo-cache": "1.3.4",
"apollo-cache": "1.3.5",
"apollo-link": "^1.0.0",
"apollo-utilities": "1.3.3",
"apollo-utilities": "1.3.4",
"symbol-observable": "^1.0.2",
"ts-invariant": "^0.4.0",
"tslib": "^1.10.0",
"zen-observable": "^0.8.0"
},
"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": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q=="
}
}
},
"apollo-link": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.13.tgz",
"integrity": "sha512-+iBMcYeevMm1JpYgwDEIDt/y0BB7VWyvlm/7x+TIPNLHCTCMgcEgDuW5kH86iQZWo0I7mNwQiTOz+/3ShPFmBw==",
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.14.tgz",
"integrity": "sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg==",
"requires": {
"apollo-utilities": "^1.3.0",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3",
"zen-observable-ts": "^0.8.20"
"zen-observable-ts": "^0.8.21"
}
},
"apollo-link-error": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/apollo-link-error/-/apollo-link-error-1.1.12.tgz",
"integrity": "sha512-psNmHyuy3valGikt/XHJfe0pKJnRX19tLLs6P6EHRxg+6q6JMXNVLYPaQBkL0FkwdTCB0cbFJAGRYCBviG8TDA==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/apollo-link-error/-/apollo-link-error-1.1.13.tgz",
"integrity": "sha512-jAZOOahJU6bwSqb2ZyskEK1XdgUY9nkmeclCrW7Gddh1uasHVqmoYc4CKdb0/H0Y1J9lvaXKle2Wsw/Zx1AyUg==",
"requires": {
"apollo-link": "^1.2.13",
"apollo-link-http-common": "^0.2.15",
"apollo-link": "^1.2.14",
"apollo-link-http-common": "^0.2.16",
"tslib": "^1.9.3"
}
},
"apollo-link-http": {
"version": "1.5.16",
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.16.tgz",
"integrity": "sha512-IA3xA/OcrOzINRZEECI6IdhRp/Twom5X5L9jMehfzEo2AXdeRwAMlH5LuvTZHgKD8V1MBnXdM6YXawXkTDSmJw==",
"version": "1.5.17",
"resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.17.tgz",
"integrity": "sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg==",
"requires": {
"apollo-link": "^1.2.13",
"apollo-link-http-common": "^0.2.15",
"apollo-link": "^1.2.14",
"apollo-link-http-common": "^0.2.16",
"tslib": "^1.9.3"
}
},
"apollo-link-http-common": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.15.tgz",
"integrity": "sha512-+Heey4S2IPsPyTf8Ag3PugUupASJMW894iVps6hXbvwtg1aHSNMXUYO5VG7iRHkPzqpuzT4HMBanCTXPjtGzxg==",
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz",
"integrity": "sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg==",
"requires": {
"apollo-link": "^1.2.13",
"apollo-link": "^1.2.14",
"ts-invariant": "^0.4.0",
"tslib": "^1.9.3"
}
@ -11762,9 +11763,9 @@
}
},
"graphql-tag": {
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.1.tgz",
"integrity": "sha512-jApXqWBzNXQ8jYa/HLkZJaVw9jgwNqZkywa2zfFn16Iv1Zb7ELNHkJaXHR7Quvd5SIGsy6Ny7SUKATgnu05uEg=="
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.3.tgz",
"integrity": "sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA=="
},
"growly": {
"version": "1.3.0",
@ -26657,9 +26658,9 @@
"integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ=="
},
"zen-observable-ts": {
"version": "0.8.20",
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.20.tgz",
"integrity": "sha512-2rkjiPALhOtRaDX6pWyNqK1fnP5KkJJybYebopNSn6wDG1lxBoFs2+nwwXKoA6glHIrtwrfBBy6da0stkKtTAA==",
"version": "0.8.21",
"resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz",
"integrity": "sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg==",
"requires": {
"tslib": "^1.9.3",
"zen-observable": "^0.8.0"

View file

@ -8,7 +8,11 @@
"@material-ui/icons": "4.9.1",
"@material-ui/lab": "^4.0.0-alpha.47",
"@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",
"bignumber.js": "9.0.0",
"classnames": "2.2.6",
@ -17,6 +21,7 @@
"formik": "2.1.4",
"fuse.js": "^3.4.6",
"graphql": "^14.5.8",
"graphql-tag": "^2.10.3",
"jss-plugin-extend": "^10.0.0",
"libphonenumber-js": "^1.7.50",
"moment": "2.24.0",

View file

@ -6,42 +6,20 @@ import {
MuiThemeProvider,
makeStyles
} from '@material-ui/core/styles'
import ApolloClient from 'apollo-boost'
import { setAutoFreeze } from 'immer'
import { create } from 'jss'
import extendJss from 'jss-plugin-extend'
import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import client from 'src/utils/apollo'
import Header from './components/layout/Header'
import { tree, Routes } from './routing/routes'
import global from './styling/global'
import theme from './styling/theme'
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') {
const whyDidYouRender = require('@welldone-software/why-did-you-render')
whyDidYouRender(React)

View file

@ -3,41 +3,53 @@ import {
DialogActions,
DialogContent,
DialogContentText,
DialogTitle as MuiDialogTitle,
IconButton,
makeStyles
} from '@material-ui/core'
import React, { useState, memo } from 'react'
import React, { useEffect, useState, memo } from 'react'
import { Button } from '../components/buttons'
import { ReactComponent as CloseIcon } from '../styling/icons/action/close/zodiac.svg'
import { spacer } from '../styling/variables'
import { Button, IconButton } from 'src/components/buttons'
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
import { TextInput } from './inputs'
import { H4, P } from './typography'
const useStyles = makeStyles({
label: {
fontSize: 16
},
spacing: {
padding: 32
},
wrapper: {
display: 'flex'
},
title: {
margin: [[20, 0, 24, 16]]
},
closeButton: {
position: 'absolute',
right: spacer,
top: spacer
padding: 0,
margin: [[12, 12, 'auto', 'auto']]
// position: 'absolute',
// right: spacer,
// top: spacer
}
})
export const DialogTitle = ({ children, onClose }) => {
const classes = useStyles()
return (
<MuiDialogTitle>
<div className={classes.wrapper}>
{children}
{onClose && (
<IconButton
size={16}
aria-label="close"
className={classes.closeButton}
onClick={onClose}>
<CloseIcon />
</IconButton>
)}
</MuiDialogTitle>
</div>
)
}
@ -49,9 +61,12 @@ export const ConfirmDialog = memo(
toBeConfirmed,
onConfirmed,
onDissmised,
className,
...props
}) => {
const classes = useStyles()
const [value, setValue] = useState('')
useEffect(() => setValue(''), [open])
const handleChange = event => {
setValue(event.target.value)
}
@ -59,14 +74,14 @@ export const ConfirmDialog = memo(
return (
<Dialog open={open} aria-labelledby="form-dialog-title" {...props}>
<DialogTitle id="customized-dialog-title" onClose={onDissmised}>
<H4>{title}</H4>
<H4 className={classes.title}>{title}</H4>
{subtitle && (
<DialogContentText>
<P>{subtitle}</P>
</DialogContentText>
)}
</DialogTitle>
<DialogContent>
<DialogContent className={className}>
<TextInput
label={`Write '${toBeConfirmed}' to confirm`}
name="confirm-input"
@ -78,11 +93,11 @@ export const ConfirmDialog = memo(
value={value}
touched={{}}
error={toBeConfirmed !== value}
InputLabelProps={{ shrink: true }}
InputLabelProps={{ shrink: true, className: classes.label }}
onChange={handleChange}
/>
</DialogContent>
<DialogActions>
<DialogActions classes={{ spacing: classes.spacing }}>
<Button
color="green"
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 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 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 Popper from './Popper'
import { Link } from './buttons'
import DateRangePicker from './date-range-picker/DateRangePicker'
import { RadioGroup } from './inputs'
import typographyStyles from './typography/styles'
@ -123,30 +125,26 @@ const styles = {
}
const useStyles = makeStyles(styles)
const ALL = 'all'
const RANGE = 'range'
const LogsDownloaderPopover = ({
id,
name,
open,
anchorEl,
getTimestamp,
logs,
title
}) => {
const radioButtonAll = 'all'
const radioButtonRange = 'range'
const [selectedRadio, setSelectedRadio] = useState(radioButtonAll)
const [range, setRange] = useState(null)
const LogsDownloaderPopover = ({ name, getTimestamp, logs, title }) => {
const [selectedRadio, setSelectedRadio] = useState(ALL)
const [range, setRange] = useState({ from: null, to: null })
const [anchorEl, setAnchorEl] = useState(null)
const classes = useStyles()
const dateRangePickerClasses = {
[classes.dateRangePickerShowing]: selectedRadio === radioButtonRange,
[classes.dateRangePickerHidden]: selectedRadio === radioButtonAll
[classes.dateRangePickerShowing]: selectedRadio === RANGE,
[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(
(from, to) => {
@ -164,7 +162,7 @@ const LogsDownloaderPopover = ({
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 blob = new window.Blob([text], {
type: 'text/plain;charset=utf-8'
@ -173,7 +171,7 @@ const LogsDownloaderPopover = ({
return
}
if (selectedRadio === radioButtonRange) {
if (selectedRadio === RANGE) {
const text = logs
.filter(log =>
moment(getTimestamp(log)).isBetween(range.from, range.to, 'day', '[]')
@ -190,12 +188,27 @@ const LogsDownloaderPopover = ({
}
}
const handleOpenRangePicker = event => {
setAnchorEl(anchorEl ? null : event.currentTarget)
}
const radioButtonOptions = [
{ display: 'All logs', code: radioButtonAll },
{ display: 'Date range', code: radioButtonRange }
{ display: 'All logs', code: ALL },
{ display: 'Date range', code: RANGE }
]
const open = Boolean(anchorEl)
const id = open ? 'date-range-popover' : undefined
return (
<>
<FeatureButton
Icon={Download}
InverseIcon={DownloadInverseIcon}
aria-describedby={id}
variant="contained"
onClick={handleOpenRangePicker}
/>
<Popper id={id} open={open} anchorEl={anchorEl} placement="bottom">
<div className={classes.popoverContent}>
<div className={classes.popoverHeader}>{title}</div>
@ -209,7 +222,7 @@ const LogsDownloaderPopover = ({
className={classes.radioButtons}
/>
</div>
{selectedRadio === radioButtonRange && (
{selectedRadio === RANGE && (
<div className={classnames(dateRangePickerClasses)}>
<div className={classes.dateContainerWrapper}>
{range && (
@ -235,6 +248,7 @@ const LogsDownloaderPopover = ({
</div>
</div>
</Popper>
</>
)
}

View file

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

View file

@ -5,7 +5,13 @@ import React, { useState } from 'react'
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 styles = {
@ -27,7 +33,24 @@ const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => {
borderLeft: [['2em', 'solid', 'transparent']],
borderRight: [['2em', 'solid', 'transparent']],
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: {
bottom: 0,
@ -36,7 +59,24 @@ const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => {
borderLeft: [['2em', 'solid', 'transparent']],
borderRight: [['2em', 'solid', 'transparent']],
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: {
left: 0,
@ -81,6 +121,10 @@ const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => {
enabled: true,
boundariesElement: 'scrollParent'
},
offset: {
enabled: true,
offset: '0, 10'
},
arrow: {
enabled: true,
element: arrowRef
@ -94,7 +138,7 @@ const Popover = ({ children, bgColor = white, arrowSize = 7, ...props }) => {
modifiers={modifiers}
className={classes.popover}
{...props}>
<Paper className={classes.root}>
<Paper className={classnames(classes.root, className)}>
<span className={classnames(arrowClasses)} ref={setArrowRef} />
{children}
</Paper>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import React, { memo } from 'react'
import baseButtonStyles from './BaseButton.styles'
const { baseButton } = baseButtonStyles
const { baseButton, primary } = baseButtonStyles
const styles = {
button: {
@ -12,19 +12,48 @@ const styles = {
borderRadius: baseButton.height / 2,
outline: 0,
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 SimpleButton = memo(({ className, children, color, size, ...props }) => {
const SimpleButton = memo(
({ className, Icon, InverseIcon, children, color, size, ...props }) => {
const classes = useStyles()
return (
<button className={classnames(classes.button, className)} {...props}>
<button
className={classnames(classes.primary, classes.button, className)}
{...props}>
{Icon && (
<div className={classes.buttonIcon}>
<Icon />
</div>
)}
{InverseIcon && (
<div
className={classnames(
classes.buttonIcon,
classes.buttonIconActive
)}>
<InverseIcon />
</div>
)}
{children}
</button>
)
})
}
)
export default SimpleButton

View file

@ -1,10 +1,35 @@
import * as R from 'ramda'
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 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 {
elements,
@ -16,15 +41,35 @@ const Header = () => {
toggleWidth,
DEFAULT_COL_SIZE
} = useContext(TableCtx)
const mapElement2 = (it, idx) => {
const { width, elements, name } = it
if (elements && elements.length) {
return (
<THead>
{elements.map(
({ name, width = DEFAULT_COL_SIZE, header, textAlign }, idx) => (
<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 (
<HeaderElement>
{innerElements.map(mapElement2)}
{enableEdit && (
<Td header width={editWidth} textAlign="center">
Edit
@ -40,7 +85,7 @@ const Header = () => {
Enable
</Td>
)}
</THead>
</HeaderElement>
)
}

View file

@ -83,7 +83,7 @@ const ActionCol = ({ disabled, editing }) => {
)
}
const ECol = ({ editing, config }) => {
const ECol = ({ editing, config, extraPaddingRight, extraPaddingLeft }) => {
const {
name,
input,
@ -115,7 +115,11 @@ const ECol = ({ editing, config }) => {
return (
<Td
className={{ [classes.withSuffix]: suffix }}
className={{
[classes.extraPaddingRight]: extraPaddingRight,
[classes.extraPaddingLeft]: extraPaddingLeft,
[classes.withSuffix]: suffix
}}
width={width}
size={size}
bold={bold}
@ -162,13 +166,31 @@ const ERow = ({ editing, disabled }) => {
const shouldStripe = stripeWhen && stripeWhen(values) && !editing
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 (
<Tr
size={rowSize}
error={errors && errors.length}
errorMessage={errors && errors.toString()}>
{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) && (
<ActionCol disabled={disabled} editing={editing} />

View file

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

View file

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

View file

@ -31,11 +31,13 @@ export default {
color: white,
display: 'table-row'
},
thDoubleLevel: {
padding: [[0, spacer * 2]],
thDoubleLevel: ({ width }) => ({
width,
display: 'table-cell',
'& > :first-child': {
margin: [[0, 10]],
extend: label1,
fontWeight: 700,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
@ -45,14 +47,12 @@ export default {
height: 28
},
'& > :last-child': {
padding: [[0, 11]],
display: 'table-cell',
verticalAlign: 'middle',
height: tableDoubleHeaderHeight - 28,
'& > div': {
display: 'inline-block'
height: tableDoubleHeaderHeight - 28
}
}
},
}),
cellDoubleLevel: {
display: 'flex',
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 { useSelect } from 'downshift'
import React from 'react'
import { ReactComponent as Arrowdown } from '../../../styling/icons/action/arrow/regular.svg'
import { startCase } from '../../../utils/string'
import { ReactComponent as Arrowdown } from 'src/styling/icons/action/arrow/regular.svg'
import { startCase } from 'src/utils/string'
import styles from './Select.styles'

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import Autocomplete from './Autocomplete'
import Checkbox from './Checkbox'
import RadioGroup from './RadioGroup'
import SecretInput from './SecretInput'
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
})}
onClick={() => onClick(it)}>
{itemRender ? itemRender(it) : displayName(it)}
{itemRender ? itemRender(it, isSelected(it)) : displayName(it)}
</div>
))}
{children}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,15 +1,30 @@
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 React, { useState } from 'react'
import HelpTooltip from 'src/components/HelpTooltip'
import { NamespacedTable as EditableTable } from 'src/components/editableTable'
import { Switch } from 'src/components/inputs'
import TitleSection from 'src/components/layout/TitleSection'
import { P, Label2 } from 'src/components/typography'
import { fromNamespace, toNamespace } from 'src/utils/config'
import Wizard from './Wizard'
import { DenominationsSchema, getElements } from './helper'
const useStyles = makeStyles({
fudgeFactor: {
display: 'flex',
alignItems: 'center',
marginRight: 156
},
switchLabel: {
margin: 6
}
})
const SAVE_CONFIG = gql`
mutation Save($config: JSONObject) {
saveConfig(config: $config)
@ -30,6 +45,7 @@ const GET_INFO = gql`
`
const CashOut = ({ name: SCREEN_KEY }) => {
const classes = useStyles()
const [wizard, setWizard] = useState(false)
const [error, setError] = useState(false)
const { data } = useQuery(GET_INFO)
@ -47,6 +63,7 @@ const CashOut = ({ name: SCREEN_KEY }) => {
}
const config = data?.config && fromNamespace(SCREEN_KEY)(data.config)
const fudgeFactorActive = config?.fudgeFactorActive ?? false
const locale = data?.config && fromNamespace('locale')(data.config)
const machines = data?.machines ?? []
@ -58,7 +75,31 @@ const CashOut = ({ name: SCREEN_KEY }) => {
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
name="test"
namespaces={R.map(R.path(['deviceId']))(machines)}

View file

@ -57,14 +57,13 @@ const WizardStep = ({
</div>
</Form>
</Formik>
// TODO: there was a disabled link here showing the currency code; restore it
)}
{lastStep && (
<>
<P>
When enabling cash out, your bill count will be authomatically set
to zero. Make sure you physically put cash inside the cashboxes to
When enabling cash out, your bill count will be automatically set 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,
make sure you set the correct cash out bill count for this machine
on your Cashboxes tab under Maintenance.
@ -72,7 +71,7 @@ const WizardStep = ({
<P>
When enabling cash out, default commissions will be set. To change
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.
</P>
<div className={classes.submit}>

View file

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

View file

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

View file

@ -4,7 +4,7 @@ import * as Yup from 'yup'
import { TextInput } from 'src/components/inputs/formik'
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
const getOverridesFields = getData => {
const getOverridesFields = (getData, currency) => {
const getView = (data, code, compare) => it => {
if (!data) return ''
@ -39,7 +39,7 @@ const getOverridesFields = getData => {
},
{
name: 'cryptoCurrencies',
width: 270,
width: 250,
size: 'sm',
view: displayCodeArray(cryptoData),
input: Autocomplete,
@ -54,69 +54,91 @@ const getOverridesFields = getData => {
name: 'cashIn',
display: 'Cash-in',
width: 140,
input: TextInput
input: TextInput,
textAlign: 'right',
suffix: '%'
},
{
name: 'cashOut',
display: 'Cash-out',
width: 140,
input: TextInput
input: TextInput,
textAlign: 'right',
suffix: '%'
},
{
name: 'fixedFee',
display: 'Fixed fee',
width: 140,
input: TextInput
input: TextInput,
doubleHeader: 'Cash-in only',
textAlign: 'right',
suffix: currency
},
{
name: 'minimumTx',
display: 'Minimun Tx',
width: 140,
input: TextInput
input: TextInput,
doubleHeader: 'Cash-in only',
textAlign: 'right',
suffix: currency
}
]
}
const mainFields = auxData => [
const mainFields = currency => [
{
name: 'cashIn',
display: 'Cash-in',
width: 169,
size: 'lg',
input: TextInput
input: TextInput,
suffix: '%'
},
{
name: 'cashOut',
display: 'Cash-out',
width: 169,
size: 'lg',
input: TextInput
input: TextInput,
suffix: '%'
},
{
name: 'fixedFee',
display: 'Fixed fee',
width: 169,
size: 'lg',
input: TextInput
doubleHeader: 'Cash-in only',
textAlign: 'center',
input: TextInput,
suffix: currency
},
{
name: 'minimumTx',
display: 'Minimun Tx',
width: 169,
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)
return getOverridesFields(getData)
return getOverridesFields(getData, currency)
}
const schema = Yup.object().shape({
cashIn: Yup.number().required('Required'),
cashOut: Yup.number().required('Required'),
cashIn: Yup.number()
.max(100)
.required('Required'),
cashOut: Yup.number()
.max(100)
.required('Required'),
fixedFee: Yup.number().required('Required'),
minimumTx: Yup.number().required('Required')
})
@ -124,8 +146,12 @@ const schema = Yup.object().shape({
const OverridesSchema = Yup.object().shape({
machine: Yup.string().required('Required'),
cryptoCurrencies: Yup.array().required('Required'),
cashIn: Yup.number().required('Required'),
cashOut: Yup.number().required('Required'),
cashIn: Yup.number()
.max(100)
.required('Required'),
cashOut: Yup.number()
.max(100)
.required('Required'),
fixedFee: 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 React, { memo } from 'react'
import { useQuery, useMutation } from '@apollo/react-hooks'
import { gql } from 'apollo-boost'
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 {
OVERRIDE_AUTHORIZED,
OVERRIDE_REJECTED
} 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 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 {
CustomerDetails,
IdDataCard,
@ -25,9 +25,8 @@ import {
IdCardPhotoCard,
TransactionsList
} from './components'
import { mainStyles } from './Customers.styles'
const useStyles = makeStyles(mainStyles)
const useStyles = makeStyles(styles)
const GET_CUSTOMER = gql`
query customer($customerId: ID!) {
@ -96,8 +95,7 @@ const CustomerProfile = memo(() => {
const { data: customerResponse, refetch: getCustomer } = useQuery(
GET_CUSTOMER,
{
variables: { customerId },
fetchPolicy: 'no-cache'
variables: { customerId }
}
)
@ -125,24 +123,25 @@ const CustomerProfile = memo(() => {
return (
<>
<Breadcrumbs
classes={{ root: classes.breadcrumbs }}
separator={<NavigateNextIcon fontSize="small" />}
aria-label="breadcrumb">
<Label1
noMargin
className={classes.labelLink}
onClick={() => history.push('/compliance/customers')}>
Customers
</Label1>
<Label1 className={classes.bold}>
{R.path(['name'])(customerData)}
</Label1>
<Label2 noMargin className={classes.labelLink}>
Rafael{R.path(['name'])(customerData)}
</Label2>
</Breadcrumbs>
<div>
<div className={classes.header}>
<Box display="flex" justifyContent="space-between">
<CustomerDetails customer={customerData} />
<div className={classes.rightAligned}>
<Label1 className={classes.label1}>Actions</Label1>
<div>
<Label1 className={classes.actionLabel}>Actions</Label1>
<ActionButton
className={classes.actionButton}
color="primary"
Icon={blocked ? AuthorizeIcon : BlockIcon}
InverseIcon={blocked ? AuthorizeReversedIcon : BlockReversedIcon}
@ -156,8 +155,8 @@ const CustomerProfile = memo(() => {
{`${blocked ? 'Authorize' : 'Block'} customer`}
</ActionButton>
</div>
</div>
<div className={classes.rowCenterAligned}>
</Box>
<Box display="flex">
<IdDataCard
customerData={customerData}
updateCustomer={updateCustomer}
@ -170,7 +169,7 @@ const CustomerProfile = memo(() => {
customerData={customerData}
updateCustomer={updateCustomer}
/>
</div>
</Box>
</div>
<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 { gql } from 'apollo-boost'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React from 'react'
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 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 classes = useStyles()

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import { makeStyles } from '@material-ui/core/styles'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
import * as R from 'ramda'
import React, { memo } from 'react'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
import {
PropertyCard,
@ -9,7 +9,7 @@ import {
OVERRIDE_REJECTED
} from 'src/pages/Customers/components/propertyCard'
import { mainStyles } from '../Customers.styles'
import mainStyles from '../CustomersList.styles'
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 CopyToClipboard from '../../Transactions/CopyToClipboard'
import { mainStyles } from '../Customers.styles'
import mainStyles from '../CustomersList.styles'
const useStyles = makeStyles(mainStyles)

View file

@ -1,7 +1,7 @@
import { Paper } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames'
import React, { memo } from 'react'
import { Paper } from '@material-ui/core'
import { ActionButton } from 'src/components/buttons'
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 { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import BigNumber from 'bignumber.js'
import classnames from 'classnames'
import gql from 'graphql-tag'
import moment from 'moment'
import QRCode from 'qrcode.react'
import React, { useState } from 'react'
@ -35,6 +35,7 @@ const GET_FUNDING = gql`
{
funding {
cryptoCode
errorMsg
fundingAddress
fundingAddressUrl
confirmedBalance
@ -55,6 +56,7 @@ const formatNumber = it => new BigNumber(it).toFormat(2)
const getConfirmedTotal = list => {
return formatNumber(
list
.filter(it => !it.errorMsg)
.map(it => new BigNumber(it.fiatConfirmedBalance))
.reduce(sumReducer, new BigNumber(0))
)
@ -63,6 +65,7 @@ const getConfirmedTotal = list => {
const getPendingTotal = list => {
return formatNumber(
list
.filter(it => !it.errorMsg)
.map(it => new BigNumber(it.fiatPending))
.reduce(sumReducer, new BigNumber(0))
)
@ -107,16 +110,29 @@ const Funding = () => {
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 (
<div className={classes.itemWrapper}>
<div className={classnames(wrapperClass)}>
<div className={classes.firstItem}>{it.display}</div>
<div className={classes.item}>
{!it.errorMsg && (
<>
<div className={classnames(itemClass)}>
{it.fiatConfirmedBalance} {it.fiatCode}
</div>
<div className={classes.item}>
<div className={classnames(itemClass)}>
{it.confirmedBalance} {it.cryptoCode}
</div>
</>
)}
</div>
)
}
@ -149,7 +165,14 @@ const Funding = () => {
</div>
)}
</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.firstSide}>
<H3>Balance ({selected.display})</H3>

View file

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

View file

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

View file

@ -37,6 +37,16 @@ export default {
fillColumn: {
width: '100%'
},
shareButton: {
margin: 8,
display: 'flex',
alignItems: 'center',
fontSize: 12,
padding: [[0, 12]]
},
shareIcon: {
marginRight: 6
},
button: {
margin: 8
},
@ -45,9 +55,9 @@ export default {
},
buttonsWrapper: {
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 { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import gql from 'graphql-tag'
import moment from 'moment'
import * as R from 'ramda'
import React, { useState } from 'react'
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
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 {
Table,
@ -18,8 +18,8 @@ import {
TableCell
} from 'src/components/table'
import { Info3 } from 'src/components/typography'
import { ReactComponent as DownloadActive } from 'src/styling/icons/button/download/white.svg'
import { ReactComponent as Download } from 'src/styling/icons/button/download/zodiac.svg'
import { ReactComponent as WhiteShareIcon } from 'src/styling/icons/circle buttons/share/white.svg'
import { ReactComponent as ShareIcon } from 'src/styling/icons/circle buttons/share/zodiac.svg'
import styles from './Logs.styles'
@ -62,7 +62,6 @@ const Logs = () => {
const [selected, setSelected] = useState(null)
const [saveMessage, setSaveMessage] = useState(null)
const [anchorEl, setAnchorEl] = useState(null)
const deviceId = selected?.deviceId
@ -76,7 +75,6 @@ const Logs = () => {
const { data: logsResponse } = useQuery(GET_MACHINE_LOGS, {
variables: { deviceId },
fetchPolicy: 'no-cache',
skip: !selected,
onCompleted: () => setSaveMessage('')
})
@ -89,13 +87,6 @@ const Logs = () => {
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 (
<>
<div className={classes.titleWrapper}>
@ -103,25 +94,17 @@ const Logs = () => {
<Title>Machine Logs</Title>
{logsResponse && (
<div className={classes.buttonsWrapper}>
<FeatureButton
Icon={Download}
InverseIcon={DownloadActive}
aria-describedby={id}
variant="contained"
onClick={handleOpenRangePicker}
/>
<LogsDowloaderPopover
title="Download logs"
name="machine-logs"
id={id}
open={open}
anchorEl={anchorEl}
logs={logsResponse.machineLogs}
getTimestamp={log => log.timestamp}
/>
<SimpleButton
className={classes.button}
className={classes.shareButton}
disabled={loading}
Icon={ShareIcon}
InverseIcon={WhiteShareIcon}
onClick={sendSnapshot}>
Share with Lamassu
</SimpleButton>

View file

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

View file

@ -1,29 +1,20 @@
import { useMutation } from '@apollo/react-hooks'
import { Dialog, DialogContent } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import classnames from 'classnames'
import gql from 'graphql-tag'
import moment from 'moment'
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 { 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'
import styles from './MachineDetailsCard.styles'
const MACHINE_ACTION = gql`
mutation MachineAction($deviceId: ID!, $action: MachineAction!) {
@ -33,30 +24,18 @@ const MACHINE_ACTION = gql`
}
`
const colDivider = {
background: zircon,
width: 2
}
const inlineChip = {
marginInlineEnd: '0.25em'
}
const useLStyles = makeStyles(labelStyles)
const useStyles = makeStyles(styles)
const Label = ({ children }) => {
const classes = useLStyles()
return <div className={classes.label}>{children}</div>
const classes = useStyles()
return <Label1 className={classes.label}>{children}</Label1>
}
const useMDStyles = makeStyles({ ...detailsRowStyles, colDivider, inlineChip })
const MachineDetailsRow = ({ it: machine }) => {
const [errorDialog, setErrorDialog] = useState(false)
const [dialogOpen, setOpen] = useState(false)
const [actionMessage, setActionMessage] = useState(null)
const classes = useMDStyles()
const classes = useStyles()
const unpairDialog = () => setOpen(true)
@ -79,16 +58,17 @@ const MachineDetailsRow = ({ it: machine }) => {
<DialogContent>{actionMessage}</DialogContent>
</Dialog>
<div className={classes.wrapper}>
<div className={classnames(classes.row)}>
<div className={classnames(classes.col)}>
<div className={classnames(classes.row)}>
<div className={classnames(classes.col, classes.col2)}>
<div className={classes.innerRow}>
<div>
<div className={classes.column1}>
<div className={classes.lastRow}>
<div className={classes.status}>
<Label>Statuses</Label>
<div>
{machine.statuses.map((status, index) => (
<Status status={status} key={index} />
<Status
className={classes.chips}
status={status}
key={index}
/>
))}
</div>
</div>
@ -96,80 +76,35 @@ const MachineDetailsRow = ({ it: machine }) => {
<Label>Lamassu Support article</Label>
<div>
{machine.statuses.map((...[, index]) => (
<span key={index} />
// TODO support articles
<span key={index}></span>
))}
</div>
</div>
<div className={classes.separator} />
</div>
</div>
</div>
</div>
<div
className={classnames(
classes.col,
classes.col2,
classes.colDivider
)}
/>
<div className={classnames(classes.col)}>
<div className={classnames(classes.row)}>
<div className={classnames(classes.col, classes.col2)}>
<div className={classes.innerRow}>
<div>
<div className={classes.column2}>
<div className={classes.row}>
<div className={classes.machineModel}>
<Label>Machine Model</Label>
<div>{machine.machineModel}</div>
<div>{machine.model ?? 'unknown'}</div>
</div>
<div className={classes.commissionWrapper}>
<Label>Address</Label>
<div>{machine.machineLocation}</div>
</div>
</div>
</div>
</div>
<div className={classnames(classes.row)}>
<div className={classnames(classes.col, classes.col2)}>
<div className={classes.innerRow}>
<div>
<Label>Paired at</Label>
<div>
{moment(machine.pairedAt).format('YYYY-MM-DD HH:mm:ss')}
</div>
</div>
<div className={classes.commissionWrapper}>
<Label>Software update</Label>
<div className={classes.innerRow}>
{machine.softwareVersion && (
<span className={classes.inlineChip}>
{machine.softwareVersion}
</span>
)}
<ActionButton
className={classes.inlineChip}
disabled
color="primary"
Icon={DownloadIcon}
InverseIcon={DownloadReversedIcon}>
Update
</ActionButton>
{machine.pairedAt
? moment(machine.pairedAt).format('YYYY-MM-DD HH:mm:ss')
: 'N/A'}
</div>
</div>
</div>
</div>
</div>
<div className={classnames(classes.row)}>
<div className={classnames(classes.col, classes.col2)}>
<div className={classes.innerRow}>
<div className={classes.lastRow}>
<div>
<Label>Printer</Label>
<div>{machine.printer || 'unknown'}</div>
</div>
<div className={classes.commissionWrapper}>
<Label>Actions</Label>
<div className={classes.innerRow}>
<div className={classes.actionRow}>
<ActionButton
className={classes.inlineChip}
className={classes.action}
color="primary"
Icon={UnpairIcon}
InverseIcon={UnpairReversedIcon}
@ -179,6 +114,7 @@ const MachineDetailsRow = ({ it: machine }) => {
</ActionButton>
<ConfirmDialog
open={dialogOpen}
className={classes.dialog}
title="Unpair this machine?"
subtitle={false}
toBeConfirmed={machine.name}
@ -196,7 +132,7 @@ const MachineDetailsRow = ({ it: machine }) => {
}}
/>
<ActionButton
className={classes.inlineChip}
className={classes.action}
color="primary"
Icon={RebootIcon}
InverseIcon={RebootReversedIcon}
@ -212,29 +148,26 @@ const MachineDetailsRow = ({ it: machine }) => {
Reboot
</ActionButton>
<ActionButton
className={classes.inlineChip}
className={classes.action}
disabled={loading}
color="primary"
Icon={ShutdownIcon}
InverseIcon={ShutdownReversedIcon}
Icon={RebootIcon}
InverseIcon={RebootReversedIcon}
onClick={() => {
machineAction({
variables: {
deviceId: machine.deviceId,
action: 'shutdown'
action: 'restartServices'
}
})
}}>
Shutdown
Restart Services
</ActionButton>
</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 { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost'
import gql from 'graphql-tag'
import moment from 'moment'
import * as R from 'ramda'
import React from 'react'
@ -20,6 +20,8 @@ const GET_MACHINES = gql`
machines {
name
deviceId
lastPing
pairedAt
paired
cashbox
cassette1
@ -42,35 +44,28 @@ const MachineStatus = () => {
const elements = [
{
header: 'Machine Name',
width: 232,
width: 250,
size: 'sm',
textAlign: 'left',
view: m => m.name
},
{
header: 'Status',
width: 349,
width: 350,
size: 'sm',
textAlign: 'left',
view: m => <MainStatus statuses={m.statuses} />
},
{
header: 'Last ping',
width: 192,
width: 200,
size: 'sm',
textAlign: 'left',
view: m => moment(m.lastPing).fromNow()
},
{
header: 'Ping Time',
width: 155,
size: 'sm',
textAlign: 'left',
view: m => m.pingTime || 'unknown'
view: m => (m.lastPing ? moment(m.lastPing).fromNow() : 'unknown')
},
{
header: 'Software Version',
width: 201,
width: 200,
size: 'sm',
textAlign: 'left',
view: m => m.softwareVersion || 'unknown'

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,14 +10,14 @@ export default {
elements: [
{
code: 'apiKey',
display: 'API Key',
display: 'Project ID',
component: TextInputFormik,
face: true,
long: true
},
{
code: 'apiSecret',
display: 'API Secret',
display: 'Project Secret',
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 classnames from 'classnames'
import moment from 'moment'
import React, { memo } from 'react'
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 CardIdIcon } from 'src/styling/icons/ID/card/zodiac.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 TxInIcon } from 'src/styling/icons/direction/cash-in.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 { onlyFirstToUpper } from 'src/utils/string'
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 classes = labelUseStyles()
const formatAddress = (address = '') => address.replace(/(.{5})/g, '$1 ')
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 classes = detailsUseStyles()
const getStatus = it => {
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 crypto = toUnit(new BigNumber(tx.cryptoAtoms), tx.cryptoCode).toFormat(
5
)
const crypto = toUnit(new BigNumber(tx.cryptoAtoms), tx.cryptoCode)
const commissionPercentage = Number.parseFloat(tx.commissionPercentage, 2)
const commission = fiat * commissionPercentage
const exchangeRate = Number(fiat / crypto).toFixed(3)
const displayExRate = `1 ${tx.cryptoCode} = ${exchangeRate} ${tx.fiatCode}`
const customer = tx.customerIdCardData && {
name: `${onlyFirstToUpper(
tx.customerIdCardData.firstName
@ -52,33 +74,25 @@ const DetailsRow = ({ it: tx, ...props }) => {
)
}
const formatAddress = (address = '') => {
return address.replace(/(.{5})/g, '$1 ')
}
return (
<>
<div className={classes.wrapper}>
<div className={classnames(classes.row)}>
<div className={classnames(classes.col, classes.col1)}>
{/* Column 1 */}
<div className={classes.innerRow}>
<div>
<div className={classes.row}>
<div className={classes.direction}>
<Label>Direction</Label>
<div>
<span className={classes.txIcon}>
{tx.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />}
</span>
<span>
{tx.txClass === 'cashOut' ? 'Cash-out' : 'Cash-in'}
</span>
<span>{tx.txClass === 'cashOut' ? 'Cash-out' : 'Cash-in'}</span>
</div>
</div>
<div className={classes.availableIds}>
<Label>Available IDs</Label>
<div>
<Box display="flex" flexDirection="row">
{tx.customerPhone && (
<IDButton
className={classes.idButton}
name="phone"
Icon={PhoneIdIcon}
InverseIcon={PhoneIdInverseIcon}>
@ -87,14 +101,21 @@ const DetailsRow = ({ it: tx, ...props }) => {
)}
{tx.customerIdCardPhotoPath && !tx.customerIdCardData && (
<IDButton
popoverClassname={classes.popover}
className={classes.idButton}
name="card"
Icon={CardIdIcon}
InverseIcon={CardIdInverseIcon}>
<img alt="" src={tx.customerIdCardPhotoPath} />
<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}>
@ -118,10 +139,6 @@ const DetailsRow = ({ it: tx, ...props }) => {
<Label>ID number</Label>
<div>{customer.idCardNumber}</div>
</div>
<div>
<Label>Gender</Label>
<div />
</div>
<div>
<Label>Expiration date</Label>
<div>{customer.idCardExpirationDate}</div>
@ -135,87 +152,62 @@ const DetailsRow = ({ it: tx, ...props }) => {
name="cam"
Icon={CamIdIcon}
InverseIcon={CamIdInverseIcon}>
<img alt="" src={tx.customerFrontCameraPath} />
<img
src={`${URI}/front-camera-photo/${tx.customerFrontCameraPath}`}
alt=""
/>
</IDButton>
)}
</Box>
</div>
</div>
</div>
</div>
<div className={classnames(classes.col, classes.col2)}>
{/* Column 2 */}
<div className={classes.innerRow}>
<div>
<div className={classes.exchangeRate}>
<Label>Exchange rate</Label>
<div>
{`1 ${tx.cryptoCode} = ${Number(fiat / crypto).toFixed(3)} ${
tx.fiatCode
}`}
<div>{crypto > 0 ? displayExRate : '-'}</div>
</div>
</div>
<div className={classes.commissionWrapper}>
<div className={classes.commission}>
<Label>Commission</Label>
<div>
{`${commission} ${tx.fiatCode} (${commissionPercentage *
100} %)`}
{`${commission} ${tx.fiatCode} (${commissionPercentage * 100} %)`}
</div>
</div>
{tx.txClass === 'cashIn' && (
<div className={classes.innerRow}>
<div>
<Label>Fixed fee</Label>
<div>{Number.parseFloat(tx.cashInFee)}</div>
<div>
{tx.txClass === 'cashIn'
? `${Number.parseFloat(tx.cashInFee)} ${tx.fiatCode}`
: 'N/A'}
</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={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 className={classnames(classes.row)}>
<div className={classnames(classes.col, classes.col1)}>
{/* Column 1 */}
<div className={classes.innerRow}>
<div>
<Label>BTC address</Label>
<div>
<CopyToClipboard className={classes.cryptoAddr}>
{formatAddress(addr)}
</CopyToClipboard>
</div>
</div>
</div>
</div>
<div className={classnames(classes.col, classes.col2)}>
{/* Column 2 */}
<div className={classes.innerRow}>
<div>
<Label>Transaction ID</Label>
<div>
<CopyToClipboard className={classes.txId}>
{txHash}
</CopyToClipboard>
</div>
</div>
</div>
</div>
<div className={classnames(classes.col, classes.col3)}>
{/* Column 3 */}
<div className={classes.innerRow}>
<div>
<div className={classes.sessionId}>
<Label>Session ID</Label>
<CopyToClipboard className={classes.sessionId}>
{tx.id}
</CopyToClipboard>
<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>
</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 { makeStyles } from '@material-ui/core/styles'
import { gql } from 'apollo-boost'
import BigNumber from 'bignumber.js'
import gql from 'graphql-tag'
import moment from 'moment'
import * as R from 'ramda'
import React, { useState } from 'react'
import React from 'react'
import LogsDowloaderPopover from 'src/components/LogsDownloaderPopper'
import Title from 'src/components/Title'
import { FeatureButton } from 'src/components/buttons'
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 TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
import { toUnit } from 'src/utils/coin'
@ -21,7 +18,6 @@ import { mainStyles } from './Transactions.styles'
const useStyles = makeStyles(mainStyles)
// TODO customerIdCardData
const GET_TRANSACTIONS = gql`
{
transactions {
@ -30,7 +26,12 @@ const GET_TRANSACTIONS = gql`
txHash
toAddress
commissionPercentage
expired
machineName
operatorCompleted
sendConfirmed
dispense
hasError: error
deviceId
fiat
cashInFee
@ -49,8 +50,6 @@ const GET_TRANSACTIONS = gql`
`
const Transactions = () => {
const [anchorEl, setAnchorEl] = useState(null)
const classes = useStyles()
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 (
<>
<div className={classes.titleWrapper}>
@ -145,22 +133,11 @@ const Transactions = () => {
<Title>Transactions</Title>
{txResponse && (
<div className={classes.buttonsWrapper}>
<FeatureButton
Icon={Download}
InverseIcon={DownloadInverseIcon}
aria-describedby={id}
variant="contained"
onClick={handleOpenRangePicker}
/>
<LogsDowloaderPopover
title="Download logs"
name="transactions"
id={id}
open={open}
anchorEl={anchorEl}
logs={txResponse.transactions}
getTimestamp={tx => tx.created}
onClose={handleCloseRangePicker}
/>
</div>
)}

View file

@ -1,6 +1,6 @@
import typographyStyles from 'src/components/typography/styles'
import { offColor, white } from 'src/styling/variables'
import baseStyles from 'src/pages/Logs.styles'
import { offColor, white } from 'src/styling/variables'
const { label1, mono, p } = typographyStyles
const { titleWrapper, titleAndButtonsContainer, buttonsWrapper } = baseStyles
@ -34,66 +34,6 @@ const cpcStyles = {
}
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: {
extend: p,
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 { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { useState } from 'react'
import { v4 } from 'uuid'
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 { 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 Wizard from './Wizard'
@ -60,13 +58,6 @@ const Triggers = () => {
<div className={classes.titleWrapper}>
<div className={classes.titleAndButtonsContainer}>
<Title>Compliance Triggers</Title>
<div className={classes.buttonsWrapper}>
<FeatureButton
Icon={Configure}
InverseIcon={ConfigureInverseIcon}
variant="contained"
/>
</div>
</div>
<div className={classes.headerLabels}>
<Link color="primary" onClick={() => setWizard(true)}>

View file

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

View file

@ -36,10 +36,10 @@ const tree = [
},
children: [
{
key: 'logs',
label: 'Logs',
route: '/maintenance/logs',
component: MachineLogs
key: 'cashboxes',
label: 'Cashboxes',
route: '/maintenance/cashboxes',
component: Cashboxes
},
{
key: 'funding',
@ -48,10 +48,10 @@ const tree = [
component: Funding
},
{
key: 'server-logs',
label: 'Server',
route: '/maintenance/server-logs',
component: ServerLogs
key: 'logs',
label: 'Machine logs',
route: '/maintenance/logs',
component: MachineLogs
},
{
key: 'machine-status',
@ -60,10 +60,10 @@ const tree = [
component: MachineStatus
},
{
key: 'cashboxes',
label: 'Cashboxes',
route: '/maintenance/cashboxes',
component: Cashboxes
key: 'server-logs',
label: 'Server',
route: '/maintenance/server-logs',
component: ServerLogs
}
]
},
@ -83,7 +83,7 @@ const tree = [
},
{
key: namespaces.LOCALE,
label: 'Locale',
label: 'Locales',
route: '/settings/locale',
component: Locales
},
@ -93,12 +93,6 @@ const tree = [
route: '/settings/cash-out',
component: Cashout
},
{
key: 'services',
label: '3rd party services',
route: '/settings/3rd-party-services',
component: Services
},
{
key: namespaces.NOTIFICATIONS,
label: 'Notifications',
@ -106,16 +100,22 @@ const tree = [
component: Notifications
},
{
key: namespaces.OPERATOR_INFO,
label: 'Operator Info',
route: '/settings/operator-info',
component: OperatorInfo
key: 'services',
label: '3rd party services',
route: '/settings/3rd-party-services',
component: Services
},
{
key: namespaces.WALLETS,
label: 'Wallet',
route: '/settings/wallet-settings',
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>
<desc>Created with Sketch.</desc>
<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>
<g id="Styleguide" stroke="none" stroke-width="1" fill-rule="evenodd">
<g id="icon/action/arrow/regular">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</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>
</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 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">
<!-- Generator: Sketch 60.1 (88133) - https://sketch.com -->
<title>icon/action/arrow/regular/zodiac</title>
<desc>Created with Sketch.</desc>
<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>
</defs>
<g id="icon/action/arrow/regular/zodiac" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
<svg
width="13"
height="8"
viewBox="0 0 13 8"
xmlns="http://www.w3.org/2000/svg">
<defs>
<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>
<g fill="none">
<mask fill="white">
<use xlink:href="#path-1" />
</mask>
<use id="Mask" fill="#1B2559" fill-rule="nonzero" xlink:href="#path-1"></use>
</g>
<use xlink:href="#path-1" fill="#1B2559" />
</g>
</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:
- Large input fields wiggle on editable table edit/non-edit mode change.
- all (machines/coins/...) should be a option on some overrides
Main menu:
- do not change fonts on hover in the main menu
- make the clickable button bigger, not just text
Compliance:
- Reject Address Reuse missing
Overall:
- 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:
- 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:
- There's no place to pick a third party provider anymore.
Cashout:
- I've just added a zero conf limit column. Should it be like this?
- There's no place to pick a third party provider anymore. (sms.js, email.js)
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:
- 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
Compliance:
- Reject Address Reuse missing
- 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
Other stuff:
- sms.js
- email.js
Customers:
- Should add id and make it main part of the table? Name is not common at all
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