Merge branch 'dev' into fix/machine_upairing
|
|
@ -45,6 +45,7 @@ keypool=10000
|
||||||
prune=4000
|
prune=4000
|
||||||
daemon=0
|
daemon=0
|
||||||
addresstype=p2sh-segwit
|
addresstype=p2sh-segwit
|
||||||
|
changetype=bech32
|
||||||
walletrbf=1
|
walletrbf=1
|
||||||
bind=0.0.0.0:8332
|
bind=0.0.0.0:8332
|
||||||
rpcport=8333`
|
rpcport=8333`
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ const BINARIES = {
|
||||||
dir: 'bitcoin-22.0/bin'
|
dir: 'bitcoin-22.0/bin'
|
||||||
},
|
},
|
||||||
ETH: {
|
ETH: {
|
||||||
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.12-6c4dc6c3.tar.gz',
|
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.13-7a0c19f8.tar.gz',
|
||||||
dir: 'geth-linux-amd64-1.10.12-6c4dc6c3'
|
dir: 'geth-linux-amd64-1.10.13-7a0c19f8'
|
||||||
},
|
},
|
||||||
ZEC: {
|
ZEC: {
|
||||||
url: 'https://z.cash/downloads/zcash-4.5.1-1-linux64-debian-stretch.tar.gz',
|
url: 'https://z.cash/downloads/zcash-4.5.1-1-linux64-debian-stretch.tar.gz',
|
||||||
|
|
@ -45,8 +45,8 @@ const BINARIES = {
|
||||||
dir: 'litecoin-0.18.1/bin'
|
dir: 'litecoin-0.18.1/bin'
|
||||||
},
|
},
|
||||||
BCH: {
|
BCH: {
|
||||||
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v23.1.0/bitcoin-cash-node-23.1.0-x86_64-linux-gnu.tar.gz',
|
url: 'https://github.com/bitcoin-cash-node/bitcoin-cash-node/releases/download/v24.0.0/bitcoin-cash-node-24.0.0-x86_64-linux-gnu.tar.gz',
|
||||||
dir: 'bitcoin-cash-node-23.1.0/bin',
|
dir: 'bitcoin-cash-node-24.0.0/bin',
|
||||||
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
|
||||||
},
|
},
|
||||||
XMR: {
|
XMR: {
|
||||||
|
|
|
||||||
|
|
@ -44,5 +44,6 @@ connections=40
|
||||||
keypool=10000
|
keypool=10000
|
||||||
prune=4000
|
prune=4000
|
||||||
daemon=0
|
daemon=0
|
||||||
addresstype=p2sh-segwit`
|
addresstype=p2sh-segwit
|
||||||
|
changetype=bech32`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ const _ = require('lodash/fp')
|
||||||
const uuid = require('uuid')
|
const uuid = require('uuid')
|
||||||
|
|
||||||
function createCashboxBatch (deviceId, cashboxCount) {
|
function createCashboxBatch (deviceId, cashboxCount) {
|
||||||
if (_.isEqual(0, cashboxCount)) throw new Error('Cashbox is empty. Cashbox batch could not be created.')
|
if (_.isEqual(0, cashboxCount)) throw new Error('Cash box is empty. Cash box batch could not be created.')
|
||||||
const sql = `INSERT INTO cashbox_batches (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-in-empty') RETURNING *`
|
const sql = `INSERT INTO cashbox_batches (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-empty') RETURNING *`
|
||||||
const sql2 = `
|
const sql2 = `
|
||||||
UPDATE bills SET cashbox_batch_id=$1
|
UPDATE bills SET cashbox_batch_id=$1
|
||||||
FROM cash_in_txs
|
FROM cash_in_txs
|
||||||
|
|
@ -24,12 +24,12 @@ function updateMachineWithBatch (machineContext, oldCashboxCount) {
|
||||||
const isCassetteAmountWithinRange = _.inRange(constants.CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES, constants.CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES + 1, _.size(machineContext.cassettes))
|
const isCassetteAmountWithinRange = _.inRange(constants.CASH_OUT_MINIMUM_AMOUNT_OF_CASSETTES, constants.CASH_OUT_MAXIMUM_AMOUNT_OF_CASSETTES + 1, _.size(machineContext.cassettes))
|
||||||
if (!isValidContext && !isCassetteAmountWithinRange)
|
if (!isValidContext && !isCassetteAmountWithinRange)
|
||||||
throw new Error('Insufficient info to create a new cashbox batch')
|
throw new Error('Insufficient info to create a new cashbox batch')
|
||||||
if (_.isEqual(0, oldCashboxCount)) throw new Error('Cashbox is empty. Cashbox batch could not be created.')
|
if (_.isEqual(0, oldCashboxCount)) throw new Error('Cash box is empty. Cash box batch could not be created.')
|
||||||
|
|
||||||
return db.tx(t => {
|
return db.tx(t => {
|
||||||
const deviceId = machineContext.deviceId
|
const deviceId = machineContext.deviceId
|
||||||
const batchId = uuid.v4()
|
const batchId = uuid.v4()
|
||||||
const q1 = t.none(`INSERT INTO cashbox_batches (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-in-empty')`, [batchId, deviceId])
|
const q1 = t.none(`INSERT INTO cashbox_batches (id, device_id, created, operation_type) VALUES ($1, $2, now(), 'cash-box-empty')`, [batchId, deviceId])
|
||||||
const q2 = t.none(`UPDATE bills SET cashbox_batch_id=$1 FROM cash_in_txs
|
const q2 = t.none(`UPDATE bills SET cashbox_batch_id=$1 FROM cash_in_txs
|
||||||
WHERE bills.cash_in_txs_id = cash_in_txs.id AND
|
WHERE bills.cash_in_txs_id = cash_in_txs.id AND
|
||||||
cash_in_txs.device_id = $2 AND
|
cash_in_txs.device_id = $2 AND
|
||||||
|
|
@ -68,4 +68,10 @@ function getBillsByBatchId (id) {
|
||||||
return db.any(sql, [id])
|
return db.any(sql, [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { createCashboxBatch, updateMachineWithBatch, getBatches, getBillsByBatchId, editBatchById }
|
module.exports = {
|
||||||
|
createCashboxBatch,
|
||||||
|
updateMachineWithBatch,
|
||||||
|
getBatches,
|
||||||
|
getBillsByBatchId,
|
||||||
|
editBatchById
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -683,18 +683,18 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
|
||||||
*/
|
*/
|
||||||
function getCustomerById (id) {
|
function getCustomerById (id) {
|
||||||
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
const passableErrorCodes = _.map(Pgp.as.text, TX_PASSTHROUGH_ERROR_CODES).join(',')
|
||||||
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_override,
|
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override,
|
||||||
phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration,
|
phone, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||||
id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
id_card_photo_path, id_card_photo_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||||
sanctions_override, total_txs, total_spent, created AS last_active, fiat AS last_tx_fiat,
|
sanctions_override, total_txs, total_spent, created AS last_active, fiat AS last_tx_fiat,
|
||||||
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes
|
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes
|
||||||
FROM (
|
FROM (
|
||||||
SELECT c.id, c.authorized_override,
|
SELECT c.id, c.authorized_override,
|
||||||
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
|
||||||
c.suspended_until > now() AS is_suspended,
|
c.suspended_until > now() AS is_suspended,
|
||||||
c.front_camera_path, c.front_camera_override,
|
c.front_camera_path, c.front_camera_override, c.front_camera_at,
|
||||||
c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
|
c.phone, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
|
||||||
c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions,
|
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
|
||||||
c.sanctions_at, c.sanctions_override, c.subscriber_info, t.tx_class, t.fiat, t.fiat_code, t.created, cn.notes,
|
c.sanctions_at, c.sanctions_override, c.subscriber_info, t.tx_class, t.fiat, t.fiat_code, t.created, cn.notes,
|
||||||
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
|
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
|
||||||
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY c.id) AS total_txs,
|
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY c.id) AS total_txs,
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ function addName (pings, events, config) {
|
||||||
const statuses = [
|
const statuses = [
|
||||||
getStatus(
|
getStatus(
|
||||||
_.first(pings[machine.deviceId]),
|
_.first(pings[machine.deviceId]),
|
||||||
_.first(checkStuckScreen(events, machine.name))
|
_.first(checkStuckScreen(events, machine))
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ const bills = require('../../services/bills')
|
||||||
|
|
||||||
const resolvers = {
|
const resolvers = {
|
||||||
Query: {
|
Query: {
|
||||||
bills: () => bills.getBills()
|
bills: (...[, { filters }]) => bills.getBills(filters)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@ const { gql } = require('apollo-server-express')
|
||||||
|
|
||||||
const typeDef = gql`
|
const typeDef = gql`
|
||||||
type Bill {
|
type Bill {
|
||||||
|
id: ID
|
||||||
fiat: Int
|
fiat: Int
|
||||||
deviceId: ID
|
deviceId: ID
|
||||||
created: Date
|
created: Date
|
||||||
cashbox: Int
|
cashboxBatchId: ID
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
bills: [Bill] @auth
|
bills(filters: JSONObject): [Bill] @auth
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,27 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
const pgp = require('pg-promise')()
|
||||||
|
|
||||||
const db = require('../../db')
|
const db = require('../../db')
|
||||||
|
|
||||||
// Get all bills with device id
|
const getBills = filters => {
|
||||||
const getBills = () => {
|
const deviceStatement = !_.isNil(filters.deviceId) ? `WHERE device_id = ${pgp.as.text(filters.deviceId)}` : ``
|
||||||
return Promise.reject(new Error('This functionality hasn\'t been implemented yet'))
|
const batchStatement = filter => {
|
||||||
/* return db.any(`
|
switch (filter) {
|
||||||
SELECT d.device_id, b.fiat, b.created, d.cashbox
|
case 'none':
|
||||||
FROM cash_in_txs
|
return `WHERE b.cashbox_batch_id IS NULL`
|
||||||
INNER JOIN bills AS b ON b.cash_in_txs_id = cash_in_txs.id
|
case 'any':
|
||||||
INNER JOIN devices as d ON d.device_id = cash_in_txs.device_id
|
return `WHERE b.cashbox_batch_id IS NOT NULL`
|
||||||
ORDER BY device_id, created DESC`
|
default:
|
||||||
)
|
return _.isNil(filter) ? `` : `WHERE b.cashbox_batch_id = ${pgp.as.text(filter)}`
|
||||||
.then(res => {
|
}
|
||||||
return res.map(item => ({
|
}
|
||||||
fiat: item.fiat,
|
|
||||||
deviceId: item.device_id,
|
const sql = `SELECT b.id, b.fiat, b.fiat_code, b.created, b.cashbox_batch_id, cit.device_id AS device_id FROM bills b LEFT OUTER JOIN (
|
||||||
cashbox: item.cashbox,
|
SELECT id, device_id FROM cash_in_txs ${deviceStatement}
|
||||||
created: item.created
|
) AS cit ON cit.id = b.cash_in_txs_id ${batchStatement(filters.batch)}`
|
||||||
}))
|
|
||||||
}) */
|
return db.any(sql)
|
||||||
|
.then(res => _.map(_.mapKeys(_.camelCase), res))
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ function batch (
|
||||||
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $1)) AS expired
|
((NOT txs.send_confirmed) AND (txs.created <= now() - interval $1)) AS expired
|
||||||
FROM (SELECT *, ${cashInTx.TRANSACTION_STATES} AS txStatus FROM cash_in_txs) AS txs
|
FROM (SELECT *, ${cashInTx.TRANSACTION_STATES} AS txStatus FROM cash_in_txs) AS txs
|
||||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||||
INNER JOIN devices d ON txs.device_id = d.device_id
|
LEFT JOIN devices d ON txs.device_id = d.device_id
|
||||||
WHERE txs.created >= $2 AND txs.created <= $3 ${
|
WHERE txs.created >= $2 AND txs.created <= $3 ${
|
||||||
id !== null ? `AND txs.device_id = $6` : ``
|
id !== null ? `AND txs.device_id = $6` : ``
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +87,7 @@ function batch (
|
||||||
INNER JOIN cash_out_actions actions ON txs.id = actions.tx_id
|
INNER JOIN cash_out_actions actions ON txs.id = actions.tx_id
|
||||||
AND actions.action = 'provisionAddress'
|
AND actions.action = 'provisionAddress'
|
||||||
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
LEFT OUTER JOIN customers c ON txs.customer_id = c.id
|
||||||
INNER JOIN devices d ON txs.device_id = d.device_id
|
LEFT JOIN devices d ON txs.device_id = d.device_id
|
||||||
WHERE txs.created >= $2 AND txs.created <= $3 ${
|
WHERE txs.created >= $2 AND txs.created <= $3 ${
|
||||||
id !== null ? `AND txs.device_id = $6` : ``
|
id !== null ? `AND txs.device_id = $6` : ``
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +130,10 @@ function simplifiedBatch (data) {
|
||||||
const getCryptoAmount = it => coinUtils.toUnit(BN(it.cryptoAtoms), it.cryptoCode).toString()
|
const getCryptoAmount = it => coinUtils.toUnit(BN(it.cryptoAtoms), it.cryptoCode).toString()
|
||||||
|
|
||||||
const getProfit = it => {
|
const getProfit = it => {
|
||||||
const getCommissionFee = it => BN(it.commissionPercentage).times(BN(it.fiat))
|
const discountValue = _.isNil(it.discount) ? BN(100) : BN(100).minus(it.discount)
|
||||||
|
const discountPercentage = BN(discountValue).div(100)
|
||||||
|
const commissionPercentage = BN(it.commissionPercentage).times(discountPercentage)
|
||||||
|
const getCommissionFee = it => BN(commissionPercentage).times(BN(it.fiat))
|
||||||
if (!it.cashInFee) return getCommissionFee(it)
|
if (!it.cashInFee) return getCommissionFee(it)
|
||||||
return getCommissionFee(it).plus(BN(it.cashInFee))
|
return getCommissionFee(it).plus(BN(it.cashInFee))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,12 +85,8 @@ function buildAlerts (pings, balances, events, devices) {
|
||||||
alerts.general = _.filter(r => !r.deviceId, balances)
|
alerts.general = _.filter(r => !r.deviceId, balances)
|
||||||
_.forEach(device => {
|
_.forEach(device => {
|
||||||
const deviceId = device.deviceId
|
const deviceId = device.deviceId
|
||||||
const deviceName = device.name
|
|
||||||
const deviceEvents = events.filter(function (eventRow) {
|
|
||||||
return eventRow.device_id === deviceId
|
|
||||||
})
|
|
||||||
const ping = pings[deviceId] || []
|
const ping = pings[deviceId] || []
|
||||||
const stuckScreen = checkStuckScreen(deviceEvents, deviceName)
|
const stuckScreen = checkStuckScreen(events, device)
|
||||||
|
|
||||||
alerts.devices = _.set([deviceId, 'balanceAlerts'], _.filter(
|
alerts.devices = _.set([deviceId, 'balanceAlerts'], _.filter(
|
||||||
['deviceId', deviceId],
|
['deviceId', deviceId],
|
||||||
|
|
@ -98,7 +94,7 @@ function buildAlerts (pings, balances, events, devices) {
|
||||||
), alerts.devices)
|
), alerts.devices)
|
||||||
alerts.devices[deviceId].deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping
|
alerts.devices[deviceId].deviceAlerts = _.isEmpty(ping) ? stuckScreen : ping
|
||||||
|
|
||||||
alerts.deviceNames[deviceId] = deviceName
|
alerts.deviceNames[deviceId] = device.name
|
||||||
}, devices)
|
}, devices)
|
||||||
|
|
||||||
return alerts
|
return alerts
|
||||||
|
|
@ -110,12 +106,13 @@ function checkPings (devices) {
|
||||||
return _.zipObject(deviceIds)(pings)
|
return _.zipObject(deviceIds)(pings)
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkStuckScreen (deviceEvents, machineName) {
|
function checkStuckScreen (deviceEvents, machine) {
|
||||||
const sortedEvents = _.sortBy(
|
const lastEvent = _.pipe(
|
||||||
utils.getDeviceTime,
|
_.filter(e => e.device_id === machine.deviceId),
|
||||||
_.map(utils.parseEventNote, deviceEvents)
|
_.sortBy(utils.getDeviceTime),
|
||||||
)
|
_.map(utils.parseEventNote),
|
||||||
const lastEvent = _.last(sortedEvents)
|
_.last
|
||||||
|
)(deviceEvents)
|
||||||
|
|
||||||
if (!lastEvent) return []
|
if (!lastEvent) return []
|
||||||
|
|
||||||
|
|
@ -125,6 +122,7 @@ function checkStuckScreen (deviceEvents, machineName) {
|
||||||
if (isIdle) return []
|
if (isIdle) return []
|
||||||
|
|
||||||
const age = Math.floor(lastEvent.age)
|
const age = Math.floor(lastEvent.age)
|
||||||
|
const machineName = machine.name
|
||||||
if (age > STALE_STATE) return [{ code: STALE, state, age, machineName }]
|
if (age > STALE_STATE) return [{ code: STALE, state, age, machineName }]
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
|
||||||
|
|
@ -18,27 +18,10 @@ module.exports.up = function (next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLatest(OLD_SETTINGS_LOADER_SCHEMA_VERSION)
|
loadLatest(OLD_SETTINGS_LOADER_SCHEMA_VERSION)
|
||||||
.then(async settings => {
|
.then(settings => _.isEmpty(settings.config)
|
||||||
if (_.isEmpty(settings.config)) {
|
? next()
|
||||||
return {
|
: migrateConfig(settings)
|
||||||
settings,
|
)
|
||||||
machines: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
settings,
|
|
||||||
machines: await machineLoader.getMachineNames(settings.config)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(({ settings, machines }) => {
|
|
||||||
if (_.isEmpty(settings.config)) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
const sql = machines
|
|
||||||
? machines.map(m => `update devices set name = '${m.name}' where device_id = '${m.deviceId}'`)
|
|
||||||
: []
|
|
||||||
return db.multi(sql, () => migrateConfig(settings))
|
|
||||||
})
|
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.message === 'lamassu-server is not configured') {
|
if (err.message === 'lamassu-server is not configured') {
|
||||||
return next()
|
return next()
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,15 @@ var db = require('./db')
|
||||||
exports.up = function (next) {
|
exports.up = function (next) {
|
||||||
var sqls = [
|
var sqls = [
|
||||||
`CREATE TYPE cashbox_batch_type AS ENUM(
|
`CREATE TYPE cashbox_batch_type AS ENUM(
|
||||||
'cash-in-empty',
|
'cash-box-empty',
|
||||||
'cash-out-1-refill',
|
'cash-cassette-1-refill',
|
||||||
'cash-out-1-empty',
|
'cash-cassette-1-empty',
|
||||||
'cash-out-2-refill',
|
'cash-cassette-2-refill',
|
||||||
'cash-out-2-empty',
|
'cash-cassette-2-empty',
|
||||||
'cash-out-3-refill',
|
'cash-cassette-3-refill',
|
||||||
'cash-out-3-empty',
|
'cash-cassette-3-empty',
|
||||||
'cash-out-4-refill',
|
'cash-cassette-4-refill',
|
||||||
'cash-out-4-empty'
|
'cash-cassette-4-empty'
|
||||||
)`,
|
)`,
|
||||||
`ALTER TABLE cashbox_batches ADD COLUMN operation_type cashbox_batch_type NOT NULL`,
|
`ALTER TABLE cashbox_batches ADD COLUMN operation_type cashbox_batch_type NOT NULL`,
|
||||||
`ALTER TABLE cashbox_batches ADD COLUMN bill_count_override SMALLINT`,
|
`ALTER TABLE cashbox_batches ADD COLUMN bill_count_override SMALLINT`,
|
||||||
|
|
|
||||||
22
new-lamassu-admin/package-lock.json
generated
|
|
@ -6889,11 +6889,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apollo-upload-client": {
|
"apollo-upload-client": {
|
||||||
"version": "16.0.0",
|
"version": "13.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-16.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-13.0.0.tgz",
|
||||||
"integrity": "sha512-aLhYucyA0T8aBEQ5g+p13qnR9RUyL8xqb8FSZ7e/Kw2KUOsotLUlFluLobqaE7JSUFwc6sKfXIcwB7y4yEjbZg==",
|
"integrity": "sha512-lJ9/bk1BH1lD15WhWRha2J3+LrXrPIX5LP5EwiOUHv8PCORp4EUrcujrA3rI5hZeZygrTX8bshcuMdpqpSrvtA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"extract-files": "^11.0.0"
|
"@babel/runtime": "^7.9.2",
|
||||||
|
"apollo-link": "^1.2.12",
|
||||||
|
"apollo-link-http-common": "^0.2.14",
|
||||||
|
"extract-files": "^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apollo-utilities": {
|
"apollo-utilities": {
|
||||||
|
|
@ -12617,9 +12620,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extract-files": {
|
"extract-files": {
|
||||||
"version": "11.0.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-11.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-8.1.0.tgz",
|
||||||
"integrity": "sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ=="
|
"integrity": "sha512-PTGtfthZK79WUMk+avLmwx3NGdU8+iVFXC2NMGxKsn0MnihOG2lvumj+AZo8CTwTrwjXDgZ5tztbRlEdRjBonQ=="
|
||||||
},
|
},
|
||||||
"extsprintf": {
|
"extsprintf": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
|
|
@ -27096,6 +27099,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
|
||||||
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
|
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
|
||||||
},
|
},
|
||||||
|
"ua-parser-js": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-00y/AXhx0/SsnI51fTc0rLRmafiGOM4/O+ny10Ps7f+j/b8p/ZY11ytMgznXkOVo4GQ+KwQG5UQLkLGirsACRg=="
|
||||||
|
},
|
||||||
"unfetch": {
|
"unfetch": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
"apollo-link": "^1.2.14",
|
"apollo-link": "^1.2.14",
|
||||||
"apollo-link-error": "^1.1.13",
|
"apollo-link-error": "^1.1.13",
|
||||||
"apollo-link-http": "^1.5.17",
|
"apollo-link-http": "^1.5.17",
|
||||||
"apollo-upload-client": "^16.0.0",
|
"apollo-upload-client": "^13.0.0",
|
||||||
"axios": "0.21.1",
|
"axios": "0.21.1",
|
||||||
"base-64": "^1.0.0",
|
"base-64": "^1.0.0",
|
||||||
"bignumber.js": "9.0.0",
|
"bignumber.js": "9.0.0",
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
"react-use": "15.3.2",
|
"react-use": "15.3.2",
|
||||||
"react-virtualized": "^9.21.2",
|
"react-virtualized": "^9.21.2",
|
||||||
"sanctuary": "^2.0.1",
|
"sanctuary": "^2.0.1",
|
||||||
|
"ua-parser-js": "^1.0.2",
|
||||||
"uuid": "^7.0.2",
|
"uuid": "^7.0.2",
|
||||||
"yup": "0.32.9"
|
"yup": "0.32.9"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,6 @@ const NotificationCenter = ({
|
||||||
{!loading && buildNotifications()}
|
{!loading && buildNotifications()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.background} />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,16 @@ import {
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
background: {
|
container: {
|
||||||
position: 'absolute',
|
'@media only screen and (max-width: 1920px)': {
|
||||||
width: '100vw',
|
width: '30vw'
|
||||||
height: '100vh',
|
},
|
||||||
left: 0,
|
width: '40vw',
|
||||||
top: 0,
|
height: '110vh',
|
||||||
zIndex: -1,
|
right: 0,
|
||||||
backgroundColor: white,
|
backgroundColor: white,
|
||||||
boxShadow: '0 0 14px 0 rgba(0, 0, 0, 0.24)'
|
boxShadow: '0 0 14px 0 rgba(0, 0, 0, 0.24)'
|
||||||
},
|
},
|
||||||
container: {
|
|
||||||
left: -200,
|
|
||||||
top: -42,
|
|
||||||
backgroundColor: white,
|
|
||||||
height: '110vh'
|
|
||||||
},
|
|
||||||
header: {
|
header: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between'
|
justifyContent: 'space-between'
|
||||||
|
|
@ -39,7 +33,7 @@ const styles = {
|
||||||
},
|
},
|
||||||
notificationIcon: ({ buttonCoords, xOffset }) => ({
|
notificationIcon: ({ buttonCoords, xOffset }) => ({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: buttonCoords ? buttonCoords.y - 1 : 0,
|
top: buttonCoords ? buttonCoords.y : 0,
|
||||||
left: buttonCoords ? buttonCoords.x - xOffset : 0,
|
left: buttonCoords ? buttonCoords.x - xOffset : 0,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: 'transparent',
|
background: 'transparent',
|
||||||
|
|
@ -54,21 +48,33 @@ const styles = {
|
||||||
backgroundColor: zircon
|
backgroundColor: zircon
|
||||||
},
|
},
|
||||||
notificationsList: {
|
notificationsList: {
|
||||||
width: 440,
|
|
||||||
height: '90vh',
|
height: '90vh',
|
||||||
maxHeight: '100vh',
|
maxHeight: '100vh',
|
||||||
marginTop: spacer * 3,
|
marginTop: spacer * 3,
|
||||||
marginLeft: 0,
|
marginLeft: 0,
|
||||||
marginRight: -50,
|
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
overflowX: 'hidden',
|
overflowX: 'hidden',
|
||||||
backgroundColor: white,
|
backgroundColor: white,
|
||||||
zIndex: 10
|
zIndex: 10
|
||||||
},
|
},
|
||||||
notificationRow: {
|
notificationRow: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
marginBottom: spacer / 2,
|
marginBottom: spacer / 2,
|
||||||
paddingTop: spacer * 1.5
|
paddingTop: spacer * 1.5,
|
||||||
|
'& > *': {
|
||||||
|
marginRight: 10
|
||||||
|
},
|
||||||
|
'& > *:last-child': {
|
||||||
|
marginRight: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
notificationContent: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center'
|
||||||
},
|
},
|
||||||
unread: {
|
unread: {
|
||||||
backgroundColor: spring3
|
backgroundColor: spring3
|
||||||
|
|
@ -79,6 +85,9 @@ const styles = {
|
||||||
marginLeft: spacer * 3
|
marginLeft: spacer * 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
readIconWrapper: {
|
||||||
|
flexGrow: 1
|
||||||
|
},
|
||||||
unreadIcon: {
|
unreadIcon: {
|
||||||
marginLeft: spacer,
|
marginLeft: spacer,
|
||||||
marginTop: 5,
|
marginTop: 5,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import Grid from '@material-ui/core/Grid'
|
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import prettyMs from 'pretty-ms'
|
import prettyMs from 'pretty-ms'
|
||||||
|
|
@ -8,7 +7,6 @@ import React from 'react'
|
||||||
import { Label1, Label2, TL2 } from 'src/components/typography'
|
import { Label1, Label2, TL2 } from 'src/components/typography'
|
||||||
import { ReactComponent as Wrench } from 'src/styling/icons/action/wrench/zodiac.svg'
|
import { ReactComponent as Wrench } from 'src/styling/icons/action/wrench/zodiac.svg'
|
||||||
import { ReactComponent as Transaction } from 'src/styling/icons/arrow/transaction.svg'
|
import { ReactComponent as Transaction } from 'src/styling/icons/arrow/transaction.svg'
|
||||||
import { ReactComponent as StripesSvg } from 'src/styling/icons/stripes.svg'
|
|
||||||
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/tomato.svg'
|
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/tomato.svg'
|
||||||
|
|
||||||
import styles from './NotificationCenter.styles'
|
import styles from './NotificationCenter.styles'
|
||||||
|
|
@ -54,36 +52,26 @@ const NotificationRow = ({
|
||||||
[classes.unreadIcon]: !read
|
[classes.unreadIcon]: !read
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Grid
|
<div
|
||||||
container
|
|
||||||
className={classnames(
|
className={classnames(
|
||||||
classes.notificationRow,
|
classes.notificationRow,
|
||||||
!read && valid ? classes.unread : ''
|
!read && valid ? classes.unread : ''
|
||||||
)}>
|
)}>
|
||||||
<Grid item xs={2} className={classes.notificationRowIcon}>
|
<div className={classes.notificationRowIcon}>{icon}</div>
|
||||||
{icon}
|
<div className={classes.notificationContent}>
|
||||||
</Grid>
|
|
||||||
<Grid item container xs={7} direction="row">
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Label2 className={classes.notificationTitle}>
|
<Label2 className={classes.notificationTitle}>
|
||||||
{notificationTitle}
|
{notificationTitle}
|
||||||
</Label2>
|
</Label2>
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<TL2 className={classes.notificationBody}>{message}</TL2>
|
<TL2 className={classes.notificationBody}>{message}</TL2>
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Label1 className={classes.notificationSubtitle}>{age}</Label1>
|
<Label1 className={classes.notificationSubtitle}>{age}</Label1>
|
||||||
</Grid>
|
</div>
|
||||||
</Grid>
|
<div className={classes.readIconWrapper}>
|
||||||
<Grid item xs={3} style={{ zIndex: 1 }}>
|
|
||||||
<div
|
<div
|
||||||
onClick={() => toggleClear(id)}
|
onClick={() => toggleClear(id)}
|
||||||
className={classnames(iconClass)}
|
className={classnames(iconClass)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</div>
|
||||||
{!valid && <StripesSvg className={classes.stripes} />}
|
</div>
|
||||||
</Grid>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ const Tr = ({
|
||||||
onClick,
|
onClick,
|
||||||
error,
|
error,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
|
shouldShowError,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
size,
|
size,
|
||||||
|
|
@ -99,7 +100,9 @@ const Tr = ({
|
||||||
<Card className={classnames(classNames, className)} onClick={onClick}>
|
<Card className={classnames(classNames, className)} onClick={onClick}>
|
||||||
<CardContent classes={cardClasses}>
|
<CardContent classes={cardClasses}>
|
||||||
<div className={classes.mainContent}>{children}</div>
|
<div className={classes.mainContent}>{children}</div>
|
||||||
{error && <div className={classes.errorContent}>{errorMessage}</div>}
|
{error && shouldShowError && (
|
||||||
|
<div className={classes.errorContent}>{errorMessage}</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,12 @@ const styles = {
|
||||||
borderRadius: '4px'
|
borderRadius: '4px'
|
||||||
},
|
},
|
||||||
focus: {
|
focus: {
|
||||||
color: primaryColor,
|
|
||||||
border: '2px solid',
|
border: '2px solid',
|
||||||
borderColor: primaryColor,
|
borderColor: primaryColor,
|
||||||
borderRadius: '4px'
|
borderRadius: '4px',
|
||||||
|
'&:focus': {
|
||||||
|
outline: 'none'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
borderColor: errorColor
|
borderColor: errorColor
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import React from 'react'
|
||||||
|
|
||||||
import Chip from 'src/components/Chip'
|
import Chip from 'src/components/Chip'
|
||||||
import { Info2, Label1, Label2 } from 'src/components/typography'
|
import { Info2, Label1, Label2 } from 'src/components/typography'
|
||||||
|
import { numberToFiatAmount } from 'src/utils/number'
|
||||||
|
|
||||||
import { cashboxStyles, gridStyles } from './Cashbox.styles'
|
import { cashboxStyles, gridStyles } from './Cashbox.styles'
|
||||||
|
|
||||||
|
|
@ -64,11 +65,9 @@ const CashIn = ({ currency, notes, total }) => {
|
||||||
<Info2 className={classes.noMarginText}>{notes} notes</Info2>
|
<Info2 className={classes.noMarginText}>{notes} notes</Info2>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.innerRow}>
|
<div className={classes.innerRow}>
|
||||||
{/* Feature on hold until this can be calculated
|
|
||||||
<Label1 className={classes.noMarginText}>
|
<Label1 className={classes.noMarginText}>
|
||||||
{total} {currency.code}
|
{total} {currency.code}
|
||||||
</Label1>
|
</Label1>
|
||||||
*/}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -112,7 +111,7 @@ const CashOut = ({
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.innerRow}>
|
<div className={classes.innerRow}>
|
||||||
<Label1 className={classes.noMarginText}>
|
<Label1 className={classes.noMarginText}>
|
||||||
{notes * denomination} {currency.code}
|
{numberToFiatAmount(notes * denomination)} {currency.code}
|
||||||
</Label1>
|
</Label1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -103,7 +103,7 @@ const Header = memo(({ tree, user }) => {
|
||||||
|
|
||||||
const handleClick = event => {
|
const handleClick = event => {
|
||||||
const coords = notifCenterButtonRef.current.getBoundingClientRect()
|
const coords = notifCenterButtonRef.current.getBoundingClientRect()
|
||||||
setNotifButtonCoords({ x: coords.x, y: coords.y })
|
setNotifButtonCoords({ x: coords.x, y: coords.y + 5 })
|
||||||
|
|
||||||
setAnchorEl(anchorEl ? null : event.currentTarget)
|
setAnchorEl(anchorEl ? null : event.currentTarget)
|
||||||
document.querySelector('#root').classList.add('root-notifcenter-open')
|
document.querySelector('#root').classList.add('root-notifcenter-open')
|
||||||
|
|
@ -132,7 +132,7 @@ const Header = memo(({ tree, user }) => {
|
||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={idx}
|
key={idx}
|
||||||
to={it.route || it.children[0].route}
|
to={!R.isNil(it.children) ? it.children[0].route : it.route}
|
||||||
isActive={match => {
|
isActive={match => {
|
||||||
if (!match) return false
|
if (!match) return false
|
||||||
setActive(it)
|
setActive(it)
|
||||||
|
|
@ -173,10 +173,16 @@ const Header = memo(({ tree, user }) => {
|
||||||
anchorEl={anchorEl}
|
anchorEl={anchorEl}
|
||||||
className={classes.popper}
|
className={classes.popper}
|
||||||
disablePortal={false}
|
disablePortal={false}
|
||||||
|
placement="bottom-end"
|
||||||
modifiers={{
|
modifiers={{
|
||||||
|
offset: {
|
||||||
|
enabled: true,
|
||||||
|
offset: '100vw'
|
||||||
|
},
|
||||||
preventOverflow: {
|
preventOverflow: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
boundariesElement: 'viewport'
|
boundariesElement: 'viewport',
|
||||||
|
padding: 0
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
<NotificationCenter
|
<NotificationCenter
|
||||||
|
|
|
||||||
|
|
@ -171,7 +171,7 @@ const styles = {
|
||||||
hasUnread: {
|
hasUnread: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 4,
|
top: 4,
|
||||||
left: 182,
|
left: 186,
|
||||||
width: '9px',
|
width: '9px',
|
||||||
height: '9px',
|
height: '9px',
|
||||||
backgroundColor: secondaryColor,
|
backgroundColor: secondaryColor,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const Row = ({
|
const Row = ({
|
||||||
id,
|
id,
|
||||||
|
index,
|
||||||
elements,
|
elements,
|
||||||
data,
|
data,
|
||||||
width,
|
width,
|
||||||
|
|
@ -48,9 +49,11 @@ const Row = ({
|
||||||
[classes.row]: true,
|
[classes.row]: true,
|
||||||
[classes.expanded]: expanded
|
[classes.expanded]: expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.rowWrapper}>
|
<div className={classes.rowWrapper}>
|
||||||
<div className={classnames({ [classes.before]: expanded && id !== 0 })}>
|
<div
|
||||||
|
className={classnames({ [classes.before]: expanded && index !== 0 })}>
|
||||||
<Tr
|
<Tr
|
||||||
size={size}
|
size={size}
|
||||||
className={classnames(trClasses)}
|
className={classnames(trClasses)}
|
||||||
|
|
@ -58,8 +61,9 @@ const Row = ({
|
||||||
expandable && expandRow(id, data)
|
expandable && expandRow(id, data)
|
||||||
onClick && onClick(data)
|
onClick && onClick(data)
|
||||||
}}
|
}}
|
||||||
error={data.error}
|
error={data.error || data.hasError}
|
||||||
errorMessage={data.errorMessage}>
|
shouldShowError={false}
|
||||||
|
errorMessage={data.errorMessage || data.hasError}>
|
||||||
{elements.map(({ view = it => it?.toString(), ...props }, idx) => (
|
{elements.map(({ view = it => it?.toString(), ...props }, idx) => (
|
||||||
<Td key={idx} {...props}>
|
<Td key={idx} {...props}>
|
||||||
{view(data)}
|
{view(data)}
|
||||||
|
|
@ -142,6 +146,7 @@ const DataTable = ({
|
||||||
width={width}
|
width={width}
|
||||||
size={rowSize}
|
size={rowSize}
|
||||||
id={data[index].id ? data[index].id : index}
|
id={data[index].id ? data[index].id : index}
|
||||||
|
index={index}
|
||||||
expWidth={expWidth}
|
expWidth={expWidth}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={data[index]}
|
data={data[index]}
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ export default {
|
||||||
confirmationCode: {
|
confirmationCode: {
|
||||||
extend: base,
|
extend: base,
|
||||||
fontSize: codeInputFontSize,
|
fontSize: codeInputFontSize,
|
||||||
fontFamily: fontPrimary,
|
fontFamily: fontSecondary,
|
||||||
fontWeight: 900
|
fontWeight: 900
|
||||||
},
|
},
|
||||||
inline: {
|
inline: {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { ReactComponent as DashLogo } from 'src/styling/logos/icon-dash-colour.s
|
||||||
import { ReactComponent as EthereumLogo } from 'src/styling/logos/icon-ethereum-colour.svg'
|
import { ReactComponent as EthereumLogo } from 'src/styling/logos/icon-ethereum-colour.svg'
|
||||||
import { ReactComponent as LitecoinLogo } from 'src/styling/logos/icon-litecoin-colour.svg'
|
import { ReactComponent as LitecoinLogo } from 'src/styling/logos/icon-litecoin-colour.svg'
|
||||||
import { ReactComponent as ZCashLogo } from 'src/styling/logos/icon-zcash-colour.svg'
|
import { ReactComponent as ZCashLogo } from 'src/styling/logos/icon-zcash-colour.svg'
|
||||||
|
import { numberToFiatAmount } from 'src/utils/number'
|
||||||
|
|
||||||
import styles from './ATMWallet.styles'
|
import styles from './ATMWallet.styles'
|
||||||
|
|
||||||
|
|
@ -51,9 +52,6 @@ const GET_OPERATOR_BY_USERNAME = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const formatCurrency = amount =>
|
|
||||||
amount.toLocaleString('en-US', { maximumFractionDigits: 2 })
|
|
||||||
|
|
||||||
const CHIPS_PER_ROW = 6
|
const CHIPS_PER_ROW = 6
|
||||||
|
|
||||||
const Assets = ({ balance, wallets, currency }) => {
|
const Assets = ({ balance, wallets, currency }) => {
|
||||||
|
|
@ -69,7 +67,7 @@ const Assets = ({ balance, wallets, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Available balance</P>
|
<P className={classes.fieldHeader}>Available balance</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{formatCurrency(balance)}
|
{numberToFiatAmount(balance)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{R.toUpper(currency)}
|
{R.toUpper(currency)}
|
||||||
|
|
@ -81,7 +79,7 @@ const Assets = ({ balance, wallets, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Total balance in wallets</P>
|
<P className={classes.fieldHeader}>Total balance in wallets</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{formatCurrency(walletFiatSum())}
|
{numberToFiatAmount(walletFiatSum())}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{R.toUpper(currency)}
|
{R.toUpper(currency)}
|
||||||
|
|
@ -93,7 +91,7 @@ const Assets = ({ balance, wallets, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Total assets</P>
|
<P className={classes.fieldHeader}>Total assets</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{formatCurrency(balance)}
|
{numberToFiatAmount(balance)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{R.toUpper(currency)}
|
{R.toUpper(currency)}
|
||||||
|
|
@ -144,17 +142,11 @@ const WalletInfoChip = ({ wallet, currency }) => {
|
||||||
<div className={classes.walletValueWrapper}>
|
<div className={classes.walletValueWrapper}>
|
||||||
<Label2 className={classes.fieldHeader}>{wallet.name} value</Label2>
|
<Label2 className={classes.fieldHeader}>{wallet.name} value</Label2>
|
||||||
<Label2 className={classes.walletValue}>
|
<Label2 className={classes.walletValue}>
|
||||||
{wallet.amount.toFixed(1).toLocaleString('en-US', {
|
{numberToFiatAmount(wallet.amount.toFixed(1))} {wallet.cryptoCode}
|
||||||
maximumFractionDigits: 2
|
|
||||||
})}{' '}
|
|
||||||
{wallet.cryptoCode}
|
|
||||||
</Label2>
|
</Label2>
|
||||||
<Label2 className={classes.fieldHeader}>Hedged value</Label2>
|
<Label2 className={classes.fieldHeader}>Hedged value</Label2>
|
||||||
<Label2 className={classes.walletValue}>
|
<Label2 className={classes.walletValue}>
|
||||||
{wallet.fiatValue.toLocaleString('en-US', {
|
{numberToFiatAmount(wallet.fiatValue)} {currency}
|
||||||
maximumFractionDigits: 2
|
|
||||||
})}{' '}
|
|
||||||
{currency}
|
|
||||||
</Label2>
|
</Label2>
|
||||||
</div>
|
</div>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,11 @@ import { Tooltip } from 'src/components/Tooltip'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import DataTable from 'src/components/tables/DataTable'
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
import { H4, Info2, P } from 'src/components/typography'
|
import { H4, Info2, P } from 'src/components/typography'
|
||||||
|
import { numberToFiatAmount } from 'src/utils/number'
|
||||||
import { formatDate } from 'src/utils/timezones'
|
import { formatDate } from 'src/utils/timezones'
|
||||||
|
|
||||||
import styles from './Accounting.styles'
|
import styles from './Accounting.styles'
|
||||||
|
|
||||||
const formatCurrency = amount =>
|
|
||||||
amount.toLocaleString('en-US', { maximumFractionDigits: 2 })
|
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const GET_OPERATOR_BY_USERNAME = gql`
|
const GET_OPERATOR_BY_USERNAME = gql`
|
||||||
|
|
@ -64,7 +62,7 @@ const Assets = ({ balance, hedgingReserve, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Pazuz fiat balance</P>
|
<P className={classes.fieldHeader}>Pazuz fiat balance</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{formatCurrency(balance)}
|
{numberToFiatAmount(balance)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{R.toUpper(currency)}
|
{R.toUpper(currency)}
|
||||||
|
|
@ -76,7 +74,7 @@ const Assets = ({ balance, hedgingReserve, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Hedging reserve</P>
|
<P className={classes.fieldHeader}>Hedging reserve</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{formatCurrency(hedgingReserve)}
|
{numberToFiatAmount(hedgingReserve)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{R.toUpper(currency)}
|
{R.toUpper(currency)}
|
||||||
|
|
@ -88,7 +86,7 @@ const Assets = ({ balance, hedgingReserve, currency }) => {
|
||||||
<P className={classes.fieldHeader}>Available balance</P>
|
<P className={classes.fieldHeader}>Available balance</P>
|
||||||
<div className={classes.totalAssetWrapper}>
|
<div className={classes.totalAssetWrapper}>
|
||||||
<Info2 noMargin className={classes.fieldValue}>
|
<Info2 noMargin className={classes.fieldValue}>
|
||||||
{formatCurrency(balance - hedgingReserve)}
|
{numberToFiatAmount(balance - hedgingReserve)}
|
||||||
</Info2>
|
</Info2>
|
||||||
<Info2 noMargin className={classes.fieldCurrency}>
|
<Info2 noMargin className={classes.fieldCurrency}>
|
||||||
{R.toUpper(currency)}
|
{R.toUpper(currency)}
|
||||||
|
|
@ -114,7 +112,7 @@ const Accounting = () => {
|
||||||
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||||
|
|
||||||
const loading = operatorLoading && configLoading
|
const loading = operatorLoading || configLoading
|
||||||
|
|
||||||
const operatorData = R.path(['operatorByUsername'], opData)
|
const operatorData = R.path(['operatorByUsername'], opData)
|
||||||
|
|
||||||
|
|
@ -143,7 +141,7 @@ const Accounting = () => {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it =>
|
view: it =>
|
||||||
`${formatCurrency(it.fiatAmount)} ${R.toUpper(it.fiatCurrency)}`
|
`${numberToFiatAmount(it.fiatAmount)} ${R.toUpper(it.fiatCurrency)}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Balance after operation',
|
header: 'Balance after operation',
|
||||||
|
|
@ -151,7 +149,9 @@ const Accounting = () => {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it =>
|
view: it =>
|
||||||
`${formatCurrency(it.fiatBalanceAfter)} ${R.toUpper(it.fiatCurrency)}`
|
`${numberToFiatAmount(it.fiatBalanceAfter)} ${R.toUpper(
|
||||||
|
it.fiatCurrency
|
||||||
|
)}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Date',
|
header: 'Date',
|
||||||
|
|
@ -170,19 +170,16 @@ const Accounting = () => {
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!loading && (
|
|
||||||
<>
|
<>
|
||||||
<TitleSection title="Accounting" />
|
<TitleSection title="Accounting" />
|
||||||
<Assets
|
<Assets
|
||||||
balance={
|
balance={operatorData.fiatBalances[operatorData.preferredFiatCurrency]}
|
||||||
operatorData.fiatBalances[operatorData.preferredFiatCurrency]
|
|
||||||
}
|
|
||||||
hedgingReserve={operatorData.hedgingReserve ?? 0}
|
hedgingReserve={operatorData.hedgingReserve ?? 0}
|
||||||
currency={operatorData.preferredFiatCurrency}
|
currency={operatorData.preferredFiatCurrency}
|
||||||
/>
|
/>
|
||||||
<H4 className={classes.tableTitle}>Fiat balance history</H4>
|
<H4 className={classes.tableTitle}>Fiat balance history</H4>
|
||||||
<DataTable
|
<DataTable
|
||||||
loading={false}
|
loading={loading}
|
||||||
emptyText="No transactions so far"
|
emptyText="No transactions so far"
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={operatorData.fundings ?? []}
|
data={operatorData.fundings ?? []}
|
||||||
|
|
@ -190,7 +187,6 @@ const Accounting = () => {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Accounting
|
export default Accounting
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { Button } from 'src/components/buttons'
|
||||||
import { TextInput } from 'src/components/inputs/formik'
|
import { TextInput } from 'src/components/inputs/formik'
|
||||||
import Sidebar from 'src/components/layout/Sidebar'
|
import Sidebar from 'src/components/layout/Sidebar'
|
||||||
import { Info2, P } from 'src/components/typography'
|
import { Info2, P } from 'src/components/typography'
|
||||||
|
import { ReactComponent as CameraIcon } from 'src/styling/icons/ID/photo/zodiac.svg'
|
||||||
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
||||||
import { ReactComponent as CompleteStageIconSpring } from 'src/styling/icons/stage/spring/complete.svg'
|
import { ReactComponent as CompleteStageIconSpring } from 'src/styling/icons/stage/spring/complete.svg'
|
||||||
import { ReactComponent as CompleteStageIconZodiac } from 'src/styling/icons/stage/zodiac/complete.svg'
|
import { ReactComponent as CompleteStageIconZodiac } from 'src/styling/icons/stage/zodiac/complete.svg'
|
||||||
|
|
@ -70,8 +71,18 @@ const QrCodeComponent = ({ classes, qrCode, name, count, onPaired }) => {
|
||||||
Scan QR code with your new cryptomat
|
Scan QR code with your new cryptomat
|
||||||
</Info2>
|
</Info2>
|
||||||
<div className={classes.qrCodeWrapper}>
|
<div className={classes.qrCodeWrapper}>
|
||||||
<div>
|
<div className={classes.qrCodeImageWrapper}>
|
||||||
<QRCode size={240} fgColor={primaryColor} value={qrCode} />
|
<QRCode
|
||||||
|
size={280}
|
||||||
|
fgColor={primaryColor}
|
||||||
|
includeMargin
|
||||||
|
value={qrCode}
|
||||||
|
className={classes.qrCodeBorder}
|
||||||
|
/>
|
||||||
|
<div className={classes.qrCodeScanMessage}>
|
||||||
|
<CameraIcon />
|
||||||
|
<P noMargin>Snap a picture and scan</P>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.qrTextWrapper}>
|
<div className={classes.qrTextWrapper}>
|
||||||
<div className={classes.qrTextInfoWrapper}>
|
<div className={classes.qrTextInfoWrapper}>
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,23 @@ const styles = {
|
||||||
},
|
},
|
||||||
errorMessage: {
|
errorMessage: {
|
||||||
color: errorColor
|
color: errorColor
|
||||||
|
},
|
||||||
|
qrCodeImageWrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
border: `5px solid ${primaryColor}`,
|
||||||
|
padding: 5,
|
||||||
|
borderRadius: 15
|
||||||
|
},
|
||||||
|
qrCodeScanMessage: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
margin: [[0, 0, 20, 20]],
|
||||||
|
'& > p': {
|
||||||
|
marginLeft: 10
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { ReactComponent as DownIcon } from 'src/styling/icons/dashboard/down.svg
|
||||||
import { ReactComponent as EqualIcon } from 'src/styling/icons/dashboard/equal.svg'
|
import { ReactComponent as EqualIcon } from 'src/styling/icons/dashboard/equal.svg'
|
||||||
import { ReactComponent as UpIcon } from 'src/styling/icons/dashboard/up.svg'
|
import { ReactComponent as UpIcon } from 'src/styling/icons/dashboard/up.svg'
|
||||||
import { fromNamespace } from 'src/utils/config'
|
import { fromNamespace } from 'src/utils/config'
|
||||||
|
import { numberToFiatAmount } from 'src/utils/number'
|
||||||
import { DAY, WEEK, MONTH } from 'src/utils/time'
|
import { DAY, WEEK, MONTH } from 'src/utils/time'
|
||||||
|
|
||||||
import styles from './Analytics.styles'
|
import styles from './Analytics.styles'
|
||||||
|
|
@ -97,9 +98,7 @@ const OverviewEntry = ({ label, value, oldValue, currency }) => {
|
||||||
<div className={classes.overviewEntry}>
|
<div className={classes.overviewEntry}>
|
||||||
<P noMargin>{label}</P>
|
<P noMargin>{label}</P>
|
||||||
<Info2 noMargin className={classes.overviewFieldWrapper}>
|
<Info2 noMargin className={classes.overviewFieldWrapper}>
|
||||||
<span>
|
<span>{numberToFiatAmount(value)}</span>
|
||||||
{value.toLocaleString('en-US', { maximumFractionDigits: 2 })}
|
|
||||||
</span>
|
|
||||||
{!!currency && ` ${currency}`}
|
{!!currency && ` ${currency}`}
|
||||||
</Info2>
|
</Info2>
|
||||||
<span className={classes.overviewGrowth}>
|
<span className={classes.overviewGrowth}>
|
||||||
|
|
@ -107,7 +106,7 @@ const OverviewEntry = ({ label, value, oldValue, currency }) => {
|
||||||
{R.lt(growthRate, 0) && <DownIcon height={10} />}
|
{R.lt(growthRate, 0) && <DownIcon height={10} />}
|
||||||
{R.equals(growthRate, 0) && <EqualIcon height={10} />}
|
{R.equals(growthRate, 0) && <EqualIcon height={10} />}
|
||||||
<P noMargin className={classnames(growthClasses)}>
|
<P noMargin className={classnames(growthClasses)}>
|
||||||
{growthRate.toLocaleString('en-US', { maximumFractionDigits: 2 })}%
|
{numberToFiatAmount(growthRate)}%
|
||||||
</P>
|
</P>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import React, { memo } from 'react'
|
||||||
import { Info2, Label3, P } from 'src/components/typography'
|
import { Info2, Label3, P } from 'src/components/typography'
|
||||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
|
import { numberToFiatAmount } from 'src/utils/number'
|
||||||
import { singularOrPlural } from 'src/utils/string'
|
import { singularOrPlural } from 'src/utils/string'
|
||||||
import { formatDate, formatDateNonUtc } from 'src/utils/timezones'
|
import { formatDate, formatDateNonUtc } from 'src/utils/timezones'
|
||||||
|
|
||||||
|
|
@ -13,9 +14,6 @@ import styles from './GraphTooltip.styles'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const formatCurrency = amount =>
|
|
||||||
amount.toLocaleString('en-US', { maximumFractionDigits: 2 })
|
|
||||||
|
|
||||||
const GraphTooltip = ({
|
const GraphTooltip = ({
|
||||||
coords,
|
coords,
|
||||||
data,
|
data,
|
||||||
|
|
@ -67,7 +65,7 @@ const GraphTooltip = ({
|
||||||
{singularOrPlural(R.length(data), 'transaction', 'transactions')}
|
{singularOrPlural(R.length(data), 'transaction', 'transactions')}
|
||||||
</P>
|
</P>
|
||||||
<P noMargin className={classes.dotOtTransactionVolume}>
|
<P noMargin className={classes.dotOtTransactionVolume}>
|
||||||
{formatCurrency(transactions.volume)} {currency} in volume
|
{numberToFiatAmount(transactions.volume)} {currency} in volume
|
||||||
</P>
|
</P>
|
||||||
<div className={classes.dotOtTransactionClasses}>
|
<div className={classes.dotOtTransactionClasses}>
|
||||||
<Label3 noMargin>
|
<Label3 noMargin>
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,10 @@ import React, { useContext } from 'react'
|
||||||
import AppContext from 'src/AppContext'
|
import AppContext from 'src/AppContext'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { H4, Label2, P, Info2 } from 'src/components/typography'
|
import { H4, Label2, P, Info2 } from 'src/components/typography'
|
||||||
|
import { numberToFiatAmount } from 'src/utils/number'
|
||||||
|
|
||||||
import styles from './Assets.styles'
|
import styles from './Assets.styles'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const GET_OPERATOR_BY_USERNAME = gql`
|
const GET_OPERATOR_BY_USERNAME = gql`
|
||||||
|
|
@ -105,7 +107,7 @@ const AssetsAmountTable = ({ title, data = [], numToRender }) => {
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell align="right">
|
<Cell align="right">
|
||||||
<P>{`${selectAmountPrefix(asset)}
|
<P>{`${selectAmountPrefix(asset)}
|
||||||
${formatCurrency(Math.abs(asset.amount))} ${
|
${numberToFiatAmount(Math.abs(asset.amount))} ${
|
||||||
asset.currency
|
asset.currency
|
||||||
}`}</P>
|
}`}</P>
|
||||||
</Cell>
|
</Cell>
|
||||||
|
|
@ -117,7 +119,9 @@ const AssetsAmountTable = ({ title, data = [], numToRender }) => {
|
||||||
<Info2>{`Total ${R.toLower(title)}`}</Info2>
|
<Info2>{`Total ${R.toLower(title)}`}</Info2>
|
||||||
</Cell>
|
</Cell>
|
||||||
<Cell align="right">
|
<Cell align="right">
|
||||||
<Info2>{`${formatCurrency(totalAmount)} ${currency}`}</Info2>
|
<Info2>{`${numberToFiatAmount(
|
||||||
|
totalAmount
|
||||||
|
)} ${currency}`}</Info2>
|
||||||
</Cell>
|
</Cell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
@ -128,9 +132,6 @@ const AssetsAmountTable = ({ title, data = [], numToRender }) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatCurrency = amount =>
|
|
||||||
amount.toLocaleString('en-US', { maximumFractionDigits: 2 })
|
|
||||||
|
|
||||||
const Assets = () => {
|
const Assets = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { userData } = useContext(AppContext)
|
const { userData } = useContext(AppContext)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useMutation, useLazyQuery } from '@apollo/react-hooks'
|
import { useMutation, useLazyQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import base64 from 'base-64'
|
import base64 from 'base-64'
|
||||||
|
import { Form, Formik } from 'formik'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import React, { useContext, useState } from 'react'
|
import React, { useContext, useState } from 'react'
|
||||||
import { useHistory } from 'react-router-dom'
|
import { useHistory } from 'react-router-dom'
|
||||||
|
|
@ -120,6 +121,9 @@ const Input2FAState = ({ state, dispatch }) => {
|
||||||
<TL1 className={classes.info}>
|
<TL1 className={classes.info}>
|
||||||
Enter your two-factor authentication code
|
Enter your two-factor authentication code
|
||||||
</TL1>
|
</TL1>
|
||||||
|
{/* TODO: refactor the 2FA CodeInput to properly use Formik */}
|
||||||
|
<Formik onSubmit={() => {}} initialValues={{}}>
|
||||||
|
<Form>
|
||||||
<CodeInput
|
<CodeInput
|
||||||
name="2fa"
|
name="2fa"
|
||||||
value={state.twoFAField}
|
value={state.twoFAField}
|
||||||
|
|
@ -128,6 +132,9 @@ const Input2FAState = ({ state, dispatch }) => {
|
||||||
error={invalidToken}
|
error={invalidToken}
|
||||||
shouldAutoFocus
|
shouldAutoFocus
|
||||||
/>
|
/>
|
||||||
|
<button onClick={handleSubmit} className={classes.enterButton} />
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
<div className={classes.twofaFooter}>
|
<div className={classes.twofaFooter}>
|
||||||
{errorMessage && <P className={classes.errorMessage}>{errorMessage}</P>}
|
{errorMessage && <P className={classes.errorMessage}>{errorMessage}</P>}
|
||||||
<Button onClick={handleSubmit} buttonClassName={classes.loginButton}>
|
<Button onClick={handleSubmit} buttonClassName={classes.loginButton}>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useQuery, useMutation } from '@apollo/react-hooks'
|
import { useQuery, useMutation } from '@apollo/react-hooks'
|
||||||
import { makeStyles, Grid } from '@material-ui/core'
|
import { makeStyles, Grid } from '@material-ui/core'
|
||||||
import Paper from '@material-ui/core/Paper'
|
import Paper from '@material-ui/core/Paper'
|
||||||
|
import { Form, Formik } from 'formik'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import QRCode from 'qrcode.react'
|
import QRCode from 'qrcode.react'
|
||||||
import React, { useReducer, useState } from 'react'
|
import React, { useReducer, useState } from 'react'
|
||||||
|
|
@ -101,6 +102,20 @@ const Reset2FA = () => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (twoFAConfirmation.length !== 6) {
|
||||||
|
setInvalidToken(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reset2FA({
|
||||||
|
variables: {
|
||||||
|
token: token,
|
||||||
|
userID: state.userID,
|
||||||
|
code: twoFAConfirmation
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
|
|
@ -152,6 +167,9 @@ const Reset2FA = () => {
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.confirm2FAInput}>
|
<div className={classes.confirm2FAInput}>
|
||||||
|
{/* TODO: refactor the 2FA CodeInput to properly use Formik */}
|
||||||
|
<Formik onSubmit={() => {}} initialValues={{}}>
|
||||||
|
<Form>
|
||||||
<CodeInput
|
<CodeInput
|
||||||
name="2fa"
|
name="2fa"
|
||||||
value={twoFAConfirmation}
|
value={twoFAConfirmation}
|
||||||
|
|
@ -160,25 +178,19 @@ const Reset2FA = () => {
|
||||||
error={invalidToken}
|
error={invalidToken}
|
||||||
shouldAutoFocus
|
shouldAutoFocus
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
className={classes.enterButton}
|
||||||
|
/>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.twofaFooter}>
|
<div className={classes.twofaFooter}>
|
||||||
{getErrorMsg() && (
|
{getErrorMsg() && (
|
||||||
<P className={classes.errorMessage}>{getErrorMsg()}</P>
|
<P className={classes.errorMessage}>{getErrorMsg()}</P>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={handleSubmit}
|
||||||
if (twoFAConfirmation.length !== 6) {
|
|
||||||
setInvalidToken(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
reset2FA({
|
|
||||||
variables: {
|
|
||||||
token: token,
|
|
||||||
userID: state.userID,
|
|
||||||
code: twoFAConfirmation
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
buttonClassName={classes.loginButton}>
|
buttonClassName={classes.loginButton}>
|
||||||
Done
|
Done
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { useMutation, useQuery, useLazyQuery } from '@apollo/react-hooks'
|
import { useMutation, useQuery, useLazyQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import base64 from 'base-64'
|
import base64 from 'base-64'
|
||||||
|
import { Form, Formik } from 'formik'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import QRCode from 'qrcode.react'
|
import QRCode from 'qrcode.react'
|
||||||
import React, { useContext, useState } from 'react'
|
import React, { useContext, useState } from 'react'
|
||||||
|
|
@ -125,6 +126,14 @@ const Setup2FAState = ({ state, dispatch }) => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (twoFAConfirmation.length !== 6) {
|
||||||
|
setInvalidToken(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setup2FA(mutationOptions)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
secret &&
|
secret &&
|
||||||
otpauth && (
|
otpauth && (
|
||||||
|
|
@ -159,6 +168,9 @@ const Setup2FAState = ({ state, dispatch }) => {
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.confirm2FAInput}>
|
<div className={classes.confirm2FAInput}>
|
||||||
|
{/* TODO: refactor the 2FA CodeInput to properly use Formik */}
|
||||||
|
<Formik onSubmit={() => {}} initialValues={{}}>
|
||||||
|
<Form>
|
||||||
<CodeInput
|
<CodeInput
|
||||||
name="2fa"
|
name="2fa"
|
||||||
value={twoFAConfirmation}
|
value={twoFAConfirmation}
|
||||||
|
|
@ -167,20 +179,15 @@ const Setup2FAState = ({ state, dispatch }) => {
|
||||||
error={invalidToken}
|
error={invalidToken}
|
||||||
shouldAutoFocus
|
shouldAutoFocus
|
||||||
/>
|
/>
|
||||||
|
<button onClick={handleSubmit} className={classes.enterButton} />
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.twofaFooter}>
|
<div className={classes.twofaFooter}>
|
||||||
{getErrorMsg() && (
|
{getErrorMsg() && (
|
||||||
<P className={classes.errorMessage}>{getErrorMsg()}</P>
|
<P className={classes.errorMessage}>{getErrorMsg()}</P>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button onClick={handleSubmit} buttonClassName={classes.loginButton}>
|
||||||
onClick={() => {
|
|
||||||
if (twoFAConfirmation.length !== 6) {
|
|
||||||
setInvalidToken(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setup2FA(mutationOptions)
|
|
||||||
}}
|
|
||||||
buttonClassName={classes.loginButton}>
|
|
||||||
Done
|
Done
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,9 @@ const styles = {
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
color: errorColor
|
color: errorColor
|
||||||
|
},
|
||||||
|
enterButton: {
|
||||||
|
display: 'none'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { spacer, white, errorColor } from 'src/styling/variables'
|
import { spacer, white } from 'src/styling/variables'
|
||||||
const styles = {
|
const styles = {
|
||||||
grid: {
|
grid: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -32,7 +32,7 @@ const styles = {
|
||||||
marginLeft: 8
|
marginLeft: 8
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
color: errorColor
|
marginTop: 20
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
import ErrorMessage from 'src/components/ErrorMessage'
|
||||||
import Modal from 'src/components/Modal'
|
import Modal from 'src/components/Modal'
|
||||||
import { Link } from 'src/components/buttons'
|
import { Link } from 'src/components/buttons'
|
||||||
import { TextInput } from 'src/components/inputs/formik'
|
import { TextInput } from 'src/components/inputs/formik'
|
||||||
|
|
@ -32,7 +33,10 @@ const BlackListModal = ({
|
||||||
LTC: 'LPKvbjwV1Kaksktzkr7TMK3FQtQEEe6Wqa',
|
LTC: 'LPKvbjwV1Kaksktzkr7TMK3FQtQEEe6Wqa',
|
||||||
DASH: 'XqQ7gU8eM76rEfey726cJpT2RGKyJyBrcn',
|
DASH: 'XqQ7gU8eM76rEfey726cJpT2RGKyJyBrcn',
|
||||||
ZEC: 't1KGyyv24eL354C9gjveBGEe8Xz9UoPKvHR',
|
ZEC: 't1KGyyv24eL354C9gjveBGEe8Xz9UoPKvHR',
|
||||||
BCH: 'qrd6za97wm03lfyg82w0c9vqgc727rhemg5yd9k3dm'
|
BCH: 'qrd6za97wm03lfyg82w0c9vqgc727rhemg5yd9k3dm',
|
||||||
|
USDT: '0x5754284f345afc66a98fbb0a0afe71e0f007b949',
|
||||||
|
XMR:
|
||||||
|
'888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H'
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -53,9 +57,8 @@ const BlackListModal = ({
|
||||||
.trim()
|
.trim()
|
||||||
.required('An address is required')
|
.required('An address is required')
|
||||||
})}
|
})}
|
||||||
onSubmit={({ address }, { resetForm }) => {
|
onSubmit={({ address }) => {
|
||||||
handleAddToBlacklist(address.trim())
|
handleAddToBlacklist(address.trim())
|
||||||
resetForm()
|
|
||||||
}}>
|
}}>
|
||||||
<Form id="address-form">
|
<Form id="address-form">
|
||||||
<H3 className={classes.modalTitle}>
|
<H3 className={classes.modalTitle}>
|
||||||
|
|
@ -63,7 +66,6 @@ const BlackListModal = ({
|
||||||
? `Blacklist ${R.toLower(selectedCoin.display)} address`
|
? `Blacklist ${R.toLower(selectedCoin.display)} address`
|
||||||
: ''}
|
: ''}
|
||||||
</H3>
|
</H3>
|
||||||
<span className={classes.error}>{errorMsg}</span>
|
|
||||||
<Field
|
<Field
|
||||||
name="address"
|
name="address"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|
@ -72,6 +74,9 @@ const BlackListModal = ({
|
||||||
placeholder={`ex: ${placeholderAddress[selectedCoin.code]}`}
|
placeholder={`ex: ${placeholderAddress[selectedCoin.code]}`}
|
||||||
component={TextInput}
|
component={TextInput}
|
||||||
/>
|
/>
|
||||||
|
{!R.isNil(errorMsg) && (
|
||||||
|
<ErrorMessage className={classes.error}>{errorMsg}</ErrorMessage>
|
||||||
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
<div className={classes.footer}>
|
<div className={classes.footer}>
|
||||||
|
|
|
||||||
|
|
@ -162,14 +162,14 @@ const WizardStep = ({
|
||||||
|
|
||||||
{lastStep && (
|
{lastStep && (
|
||||||
<div className={classes.disclaimer}>
|
<div className={classes.disclaimer}>
|
||||||
<Info2 className={classes.title}>Cash-out Bill Count</Info2>
|
<Info2 className={classes.title}>Cash Cassette Bill Count</Info2>
|
||||||
<P>
|
<P>
|
||||||
<WarningIcon className={classes.disclaimerIcon} />
|
<WarningIcon className={classes.disclaimerIcon} />
|
||||||
When enabling cash-out, your bill count will be automatically set to
|
When enabling cash-out, your bill count will be automatically set to
|
||||||
zero. Make sure you physically put cash inside the cash cassettes to
|
zero. Make sure you physically put cash inside the cash cassettes to
|
||||||
allow the machine to dispense it to your users. If you already did,
|
allow the machine to dispense it to your users. If you already did,
|
||||||
make sure you set the correct cash-out bill count for this machine
|
make sure you set the correct cash cassette bill count for this
|
||||||
on your Cash Cassettes tab under Maintenance.
|
machine on your Cash Boxes & Cassettes tab under Maintenance.
|
||||||
</P>
|
</P>
|
||||||
|
|
||||||
<Info2 className={classes.title}>Default Commissions</Info2>
|
<Info2 className={classes.title}>Default Commissions</Info2>
|
||||||
|
|
|
||||||
81
new-lamassu-admin/src/pages/Customers/CustomerPhotos.js
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { makeStyles, Paper } from '@material-ui/core'
|
||||||
|
import { format } from 'date-fns/fp'
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import { React, useState } from 'react'
|
||||||
|
|
||||||
|
import { InformativeDialog } from 'src/components/InformativeDialog'
|
||||||
|
import { Label2, H3 } from 'src/components/typography'
|
||||||
|
import { ReactComponent as CameraIcon } from 'src/styling/icons/ID/photo/comet.svg'
|
||||||
|
import { URI } from 'src/utils/apollo'
|
||||||
|
|
||||||
|
import styles from './CustomerPhotos.styles'
|
||||||
|
import PhotosCarousel from './components/PhotosCarousel'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const CustomerPhotos = ({ photosData }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const [photosDialog, setPhotosDialog] = useState(false)
|
||||||
|
const [photoClickedIndex, setPhotoClickIndex] = useState(null)
|
||||||
|
const orderedPhotosData = !R.isNil(photoClickedIndex)
|
||||||
|
? R.compose(R.flatten, R.reverse, R.splitAt(photoClickedIndex))(photosData)
|
||||||
|
: photosData
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={classes.header}>
|
||||||
|
<H3 className={classes.title}>{'Photos & files'}</H3>
|
||||||
|
</div>
|
||||||
|
<div className={classes.photosChipList}>
|
||||||
|
{photosData.map((elem, idx) => (
|
||||||
|
<PhotoCard
|
||||||
|
idx={idx}
|
||||||
|
date={elem.date}
|
||||||
|
src={`${URI}/${elem.photoDir}/${elem.path}`}
|
||||||
|
setPhotosDialog={setPhotosDialog}
|
||||||
|
setPhotoClickIndex={setPhotoClickIndex}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<InformativeDialog
|
||||||
|
open={photosDialog}
|
||||||
|
title={`Photo roll`}
|
||||||
|
data={<PhotosCarousel photosData={orderedPhotosData} />}
|
||||||
|
onDissmised={() => {
|
||||||
|
setPhotosDialog(false)
|
||||||
|
setPhotoClickIndex(null)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PhotoCard = ({
|
||||||
|
idx,
|
||||||
|
date,
|
||||||
|
src,
|
||||||
|
setPhotosDialog,
|
||||||
|
setPhotoClickIndex
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
className={classes.photoCardChip}
|
||||||
|
onClick={() => {
|
||||||
|
setPhotoClickIndex(idx)
|
||||||
|
setPhotosDialog(true)
|
||||||
|
}}>
|
||||||
|
<img className={classes.image} src={src} alt="" />
|
||||||
|
<div className={classes.footer}>
|
||||||
|
<CameraIcon />
|
||||||
|
<Label2 className={classes.date}>
|
||||||
|
{format('yyyy-MM-dd', new Date(date))}
|
||||||
|
</Label2>
|
||||||
|
</div>
|
||||||
|
</Paper>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CustomerPhotos
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
const styles = {
|
||||||
|
header: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row'
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
marginTop: 7,
|
||||||
|
marginRight: 24,
|
||||||
|
marginBottom: 32
|
||||||
|
},
|
||||||
|
photosChipList: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap'
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
objectFit: 'cover',
|
||||||
|
objectPosition: 'center',
|
||||||
|
width: 224,
|
||||||
|
height: 200,
|
||||||
|
borderTopLeftRadius: 4,
|
||||||
|
borderTopRightRadius: 4
|
||||||
|
},
|
||||||
|
photoCardChip: {
|
||||||
|
margin: [[0, 16, 0, 0]]
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
margin: [[8, 0, 0, 8]]
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
margin: [[0, 0, 8, 12]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default styles
|
||||||
|
|
@ -24,6 +24,7 @@ import { fromNamespace, namespaces } from 'src/utils/config'
|
||||||
|
|
||||||
import CustomerData from './CustomerData'
|
import CustomerData from './CustomerData'
|
||||||
import CustomerNotes from './CustomerNotes'
|
import CustomerNotes from './CustomerNotes'
|
||||||
|
import CustomerPhotos from './CustomerPhotos'
|
||||||
import styles from './CustomerProfile.styles'
|
import styles from './CustomerProfile.styles'
|
||||||
import {
|
import {
|
||||||
CustomerDetails,
|
CustomerDetails,
|
||||||
|
|
@ -31,7 +32,7 @@ import {
|
||||||
CustomerSidebar,
|
CustomerSidebar,
|
||||||
Wizard
|
Wizard
|
||||||
} from './components'
|
} from './components'
|
||||||
import { getFormattedPhone, getName } from './helper'
|
import { getFormattedPhone, getName, formatPhotosData } from './helper'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
|
@ -367,12 +368,24 @@ const CustomerProfile = memo(() => {
|
||||||
const isCustomerData = clickedItem === 'customerData'
|
const isCustomerData = clickedItem === 'customerData'
|
||||||
const isOverview = clickedItem === 'overview'
|
const isOverview = clickedItem === 'overview'
|
||||||
const isNotes = clickedItem === 'notes'
|
const isNotes = clickedItem === 'notes'
|
||||||
|
const isPhotos = clickedItem === 'photos'
|
||||||
|
|
||||||
const loading = customerLoading && configLoading
|
const frontCameraData = R.pick(['frontCameraPath', 'frontCameraAt'])(
|
||||||
|
customerData
|
||||||
|
)
|
||||||
|
const txPhotosData =
|
||||||
|
sortedTransactions &&
|
||||||
|
R.map(R.pick(['id', 'txCustomerPhotoPath', 'txCustomerPhotoAt']))(
|
||||||
|
sortedTransactions
|
||||||
|
)
|
||||||
|
|
||||||
|
const photosData = formatPhotosData(R.append(frontCameraData, txPhotosData))
|
||||||
|
|
||||||
|
const loading = customerLoading || configLoading
|
||||||
|
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||||
|
|
||||||
const classes = useStyles({ blocked })
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -406,29 +419,26 @@ const CustomerProfile = memo(() => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Label1 className={classes.actionLabel}>Actions</Label1>
|
<Label1 className={classes.actionLabel}>Actions</Label1>
|
||||||
<div>
|
<div className={classes.actionBar}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
className={classes.customerManualDataEntry}
|
className={classes.actionButton}
|
||||||
color="primary"
|
color="primary"
|
||||||
Icon={DataIcon}
|
Icon={DataIcon}
|
||||||
InverseIcon={DataReversedIcon}
|
InverseIcon={DataReversedIcon}
|
||||||
onClick={() => setWizard(true)}>
|
onClick={() => setWizard(true)}>
|
||||||
{`Manual data entry`}
|
{`Manual data entry`}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
className={classes.customerDiscount}
|
className={classes.actionButton}
|
||||||
color="primary"
|
color="primary"
|
||||||
Icon={Discount}
|
Icon={Discount}
|
||||||
InverseIcon={DiscountReversedIcon}
|
InverseIcon={DiscountReversedIcon}
|
||||||
onClick={() => {}}>
|
onClick={() => {}}>
|
||||||
{`Add individual discount`}
|
{`Add individual discount`}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{isSuspended && (
|
{isSuspended && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
className={classes.actionButton}
|
||||||
color="primary"
|
color="primary"
|
||||||
Icon={AuthorizeIcon}
|
Icon={AuthorizeIcon}
|
||||||
InverseIcon={AuthorizeReversedIcon}
|
InverseIcon={AuthorizeReversedIcon}
|
||||||
|
|
@ -442,7 +452,7 @@ const CustomerProfile = memo(() => {
|
||||||
)}
|
)}
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="primary"
|
color="primary"
|
||||||
className={classes.customerBlock}
|
className={classes.actionButton}
|
||||||
Icon={blocked ? AuthorizeIcon : BlockIcon}
|
Icon={blocked ? AuthorizeIcon : BlockIcon}
|
||||||
InverseIcon={
|
InverseIcon={
|
||||||
blocked ? AuthorizeReversedIcon : BlockReversedIcon
|
blocked ? AuthorizeReversedIcon : BlockReversedIcon
|
||||||
|
|
@ -458,7 +468,7 @@ const CustomerProfile = memo(() => {
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
color="primary"
|
color="primary"
|
||||||
className={classes.retrieveInformation}
|
className={classes.actionButton}
|
||||||
Icon={blocked ? AuthorizeIcon : BlockIcon}
|
Icon={blocked ? AuthorizeIcon : BlockIcon}
|
||||||
InverseIcon={
|
InverseIcon={
|
||||||
blocked ? AuthorizeReversedIcon : BlockReversedIcon
|
blocked ? AuthorizeReversedIcon : BlockReversedIcon
|
||||||
|
|
@ -488,6 +498,7 @@ const CustomerProfile = memo(() => {
|
||||||
justifyContent="space-between">
|
justifyContent="space-between">
|
||||||
<CustomerDetails
|
<CustomerDetails
|
||||||
customer={customerData}
|
customer={customerData}
|
||||||
|
photosData={photosData}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
setShowCompliance={() => setShowCompliance(!showCompliance)}
|
setShowCompliance={() => setShowCompliance(!showCompliance)}
|
||||||
/>
|
/>
|
||||||
|
|
@ -524,6 +535,11 @@ const CustomerProfile = memo(() => {
|
||||||
timezone={timezone}></CustomerNotes>
|
timezone={timezone}></CustomerNotes>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isPhotos && (
|
||||||
|
<div>
|
||||||
|
<CustomerPhotos photosData={photosData} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{wizard && (
|
{wizard && (
|
||||||
<Wizard
|
<Wizard
|
||||||
|
|
|
||||||
|
|
@ -15,29 +15,16 @@ export default {
|
||||||
customerDetails: {
|
customerDetails: {
|
||||||
marginBottom: 18
|
marginBottom: 18
|
||||||
},
|
},
|
||||||
customerBlock: props => ({
|
actionButton: {
|
||||||
|
margin: [[0, 0, 4, 0]],
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
margin: [[0, 0, 4, 0]],
|
justifyContent: 'center'
|
||||||
padding: [[0, props.blocked ? 35 : 48, 0]]
|
|
||||||
}),
|
|
||||||
customerDiscount: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
margin: [[0, 0, 4, 0]],
|
|
||||||
padding: [[0, 23.5, 0]]
|
|
||||||
},
|
},
|
||||||
customerManualDataEntry: {
|
actionBar: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'column',
|
||||||
margin: [[8, 0, 4, 0]],
|
width: 219
|
||||||
padding: [[0, 40.5, 0]]
|
|
||||||
},
|
|
||||||
retrieveInformation: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
margin: [[0, 0, 4, 0]],
|
|
||||||
padding: [[0, 32.5, 0]]
|
|
||||||
},
|
},
|
||||||
panels: {
|
panels: {
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,7 @@ import PhotosCard from './PhotosCard'
|
||||||
|
|
||||||
const useStyles = makeStyles(mainStyles)
|
const useStyles = makeStyles(mainStyles)
|
||||||
|
|
||||||
const CustomerDetails = memo(
|
const CustomerDetails = memo(({ customer, photosData, locale }) => {
|
||||||
({ txData, customer, locale, setShowCompliance }) => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const idNumber = R.path(['idCardData', 'documentNumber'])(customer)
|
const idNumber = R.path(['idCardData', 'documentNumber'])(customer)
|
||||||
|
|
@ -45,27 +44,14 @@ const CustomerDetails = memo(
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box display="flex">
|
<Box display="flex">
|
||||||
<PhotosCard
|
<PhotosCard photosData={photosData} />
|
||||||
frontCameraData={R.pick(['frontCameraPath', 'frontCameraAt'])(
|
|
||||||
customer
|
|
||||||
)}
|
|
||||||
txPhotosData={
|
|
||||||
txData &&
|
|
||||||
R.map(R.pick(['id', 'txCustomerPhotoPath', 'txCustomerPhotoAt']))(
|
|
||||||
txData
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Box display="flex" flexDirection="column">
|
<Box display="flex" flexDirection="column">
|
||||||
<div className={classes.name}>
|
<div className={classes.name}>
|
||||||
<IdIcon className={classes.idIcon} />
|
<IdIcon className={classes.idIcon} />
|
||||||
<H2 noMargin>
|
<H2 noMargin>
|
||||||
{name.length
|
{name.length
|
||||||
? name
|
? name
|
||||||
: getFormattedPhone(
|
: getFormattedPhone(R.path(['phone'])(customer), locale.country)}
|
||||||
R.path(['phone'])(customer),
|
|
||||||
locale.country
|
|
||||||
)}
|
|
||||||
</H2>
|
</H2>
|
||||||
</div>
|
</div>
|
||||||
<Box display="flex" mt="auto">
|
<Box display="flex" mt="auto">
|
||||||
|
|
@ -93,7 +79,6 @@ const CustomerDetails = memo(
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
export default CustomerDetails
|
export default CustomerDetails
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import { ReactComponent as NoteReversedIcon } from 'src/styling/icons/customer-n
|
||||||
import { ReactComponent as NoteIcon } from 'src/styling/icons/customer-nav/note/white.svg'
|
import { ReactComponent as NoteIcon } from 'src/styling/icons/customer-nav/note/white.svg'
|
||||||
import { ReactComponent as OverviewReversedIcon } from 'src/styling/icons/customer-nav/overview/comet.svg'
|
import { ReactComponent as OverviewReversedIcon } from 'src/styling/icons/customer-nav/overview/comet.svg'
|
||||||
import { ReactComponent as OverviewIcon } from 'src/styling/icons/customer-nav/overview/white.svg'
|
import { ReactComponent as OverviewIcon } from 'src/styling/icons/customer-nav/overview/white.svg'
|
||||||
|
import { ReactComponent as PhotosReversedIcon } from 'src/styling/icons/customer-nav/photos/comet.svg'
|
||||||
|
import { ReactComponent as Photos } from 'src/styling/icons/customer-nav/photos/white.svg'
|
||||||
|
|
||||||
import styles from './CustomerSidebar.styles.js'
|
import styles from './CustomerSidebar.styles.js'
|
||||||
|
|
||||||
|
|
@ -33,6 +35,12 @@ const CustomerSidebar = ({ isSelected, onClick }) => {
|
||||||
display: 'Notes',
|
display: 'Notes',
|
||||||
Icon: NoteIcon,
|
Icon: NoteIcon,
|
||||||
InverseIcon: NoteReversedIcon
|
InverseIcon: NoteReversedIcon
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'photos',
|
||||||
|
display: 'Photos & files',
|
||||||
|
Icon: Photos,
|
||||||
|
InverseIcon: PhotosReversedIcon
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,58 +4,21 @@ import { makeStyles } from '@material-ui/core/styles'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { memo, useState } from 'react'
|
import React, { memo, useState } from 'react'
|
||||||
|
|
||||||
import { Carousel } from 'src/components/Carousel'
|
|
||||||
import { InformativeDialog } from 'src/components/InformativeDialog'
|
import { InformativeDialog } from 'src/components/InformativeDialog'
|
||||||
import { Info2, Label1 } from 'src/components/typography'
|
import { Info2 } from 'src/components/typography'
|
||||||
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
|
import { ReactComponent as CrossedCameraIcon } from 'src/styling/icons/ID/photo/crossed-camera.svg'
|
||||||
import { URI } from 'src/utils/apollo'
|
import { URI } from 'src/utils/apollo'
|
||||||
|
|
||||||
import CopyToClipboard from '../../Transactions/CopyToClipboard'
|
|
||||||
|
|
||||||
import styles from './PhotosCard.styles'
|
import styles from './PhotosCard.styles'
|
||||||
|
import PhotosCarousel from './PhotosCarousel'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const Label = ({ children }) => {
|
const PhotosCard = memo(({ photosData }) => {
|
||||||
const classes = useStyles()
|
|
||||||
return <Label1 className={classes.label}>{children}</Label1>
|
|
||||||
}
|
|
||||||
|
|
||||||
const PhotosCard = memo(({ frontCameraData, txPhotosData }) => {
|
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [photosDialog, setPhotosDialog] = useState(false)
|
const [photosDialog, setPhotosDialog] = useState(false)
|
||||||
|
|
||||||
const mapKeys = pair => {
|
|
||||||
const [key, value] = pair
|
|
||||||
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
|
|
||||||
return ['path', value]
|
|
||||||
}
|
|
||||||
if (key === 'txCustomerPhotoAt' || key === 'frontCameraAt') {
|
|
||||||
return ['date', value]
|
|
||||||
}
|
|
||||||
return pair
|
|
||||||
}
|
|
||||||
|
|
||||||
const addPhotoDir = R.map(it => {
|
|
||||||
const hasFrontCameraData = R.has('id')(it)
|
|
||||||
return hasFrontCameraData
|
|
||||||
? { ...it, photoDir: 'operator-data/customersphotos' }
|
|
||||||
: { ...it, photoDir: 'front-camera-photo' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const standardizeKeys = R.map(
|
|
||||||
R.compose(R.fromPairs, R.map(mapKeys), R.toPairs)
|
|
||||||
)
|
|
||||||
|
|
||||||
const filterByPhotoAvailable = R.filter(
|
|
||||||
tx => !R.isNil(tx.date) && !R.isNil(tx.path)
|
|
||||||
)
|
|
||||||
|
|
||||||
const photosData = filterByPhotoAvailable(
|
|
||||||
addPhotoDir(standardizeKeys(R.append(frontCameraData, txPhotosData)))
|
|
||||||
)
|
|
||||||
|
|
||||||
const singlePhoto = R.head(photosData)
|
const singlePhoto = R.head(photosData)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -97,41 +60,4 @@ const PhotosCard = memo(({ frontCameraData, txPhotosData }) => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const PhotosCarousel = memo(({ photosData }) => {
|
|
||||||
const classes = useStyles()
|
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
|
||||||
|
|
||||||
const isFaceCustomerPhoto = !R.has('id')(photosData[currentIndex])
|
|
||||||
|
|
||||||
const slidePhoto = index => setCurrentIndex(index)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Carousel photosData={photosData} slidePhoto={slidePhoto} />
|
|
||||||
{!isFaceCustomerPhoto && (
|
|
||||||
<div className={classes.firstRow}>
|
|
||||||
<Label>Session ID</Label>
|
|
||||||
<CopyToClipboard>
|
|
||||||
{photosData && photosData[currentIndex]?.id}
|
|
||||||
</CopyToClipboard>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={classes.secondRow}>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<Label>Date</Label>
|
|
||||||
<div>{photosData && photosData[currentIndex]?.date}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Label>Taken by</Label>
|
|
||||||
<div>
|
|
||||||
{!isFaceCustomerPhoto ? 'Acceptance of T&C' : 'Compliance scan'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default PhotosCard
|
export default PhotosCard
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import typographyStyles from 'src/components/typography/styles'
|
import { zircon, backgroundColor } from 'src/styling/variables'
|
||||||
import { zircon, backgroundColor, offColor } from 'src/styling/variables'
|
|
||||||
|
|
||||||
const { p } = typographyStyles
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
photo: {
|
photo: {
|
||||||
|
|
@ -41,43 +38,5 @@ export default {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
},
|
|
||||||
label: {
|
|
||||||
color: offColor,
|
|
||||||
margin: [[0, 0, 6, 0]]
|
|
||||||
},
|
|
||||||
firstRow: {
|
|
||||||
padding: [[8]],
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column'
|
|
||||||
},
|
|
||||||
secondRow: {
|
|
||||||
extend: p,
|
|
||||||
display: 'flex',
|
|
||||||
padding: [[8]],
|
|
||||||
'& > div': {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
'& > div': {
|
|
||||||
width: 144,
|
|
||||||
height: 37,
|
|
||||||
marginBottom: 15,
|
|
||||||
marginRight: 55
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
imgWrapper: {
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
display: 'flex',
|
|
||||||
width: 550,
|
|
||||||
height: 550
|
|
||||||
},
|
|
||||||
imgInner: {
|
|
||||||
objectFit: 'cover',
|
|
||||||
objectPosition: 'center',
|
|
||||||
width: 550,
|
|
||||||
height: 550,
|
|
||||||
marginBottom: 40
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import React, { memo, useState } from 'react'
|
||||||
|
|
||||||
|
import { Carousel } from 'src/components/Carousel'
|
||||||
|
import { Label1 } from 'src/components/typography'
|
||||||
|
|
||||||
|
import CopyToClipboard from '../../Transactions/CopyToClipboard'
|
||||||
|
|
||||||
|
import styles from './PhotosCarousel.styles'
|
||||||
|
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const PhotosCarousel = memo(({ photosData }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0)
|
||||||
|
|
||||||
|
const Label = ({ children }) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
return <Label1 className={classes.label}>{children}</Label1>
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFaceCustomerPhoto = !R.has('id')(photosData[currentIndex])
|
||||||
|
|
||||||
|
const slidePhoto = index => setCurrentIndex(index)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Carousel photosData={photosData} slidePhoto={slidePhoto} />
|
||||||
|
{!isFaceCustomerPhoto && (
|
||||||
|
<div className={classes.firstRow}>
|
||||||
|
<Label>Session ID</Label>
|
||||||
|
<CopyToClipboard>
|
||||||
|
{photosData && photosData[currentIndex]?.id}
|
||||||
|
</CopyToClipboard>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={classes.secondRow}>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Label>Date</Label>
|
||||||
|
<div>{photosData && photosData[currentIndex]?.date}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Taken by</Label>
|
||||||
|
<div>
|
||||||
|
{!isFaceCustomerPhoto ? 'Acceptance of T&C' : 'Compliance scan'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default PhotosCarousel
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
|
import { offColor } from 'src/styling/variables'
|
||||||
|
|
||||||
|
const { p } = typographyStyles
|
||||||
|
|
||||||
|
export default {
|
||||||
|
label: {
|
||||||
|
color: offColor,
|
||||||
|
margin: [[0, 0, 6, 0]]
|
||||||
|
},
|
||||||
|
firstRow: {
|
||||||
|
padding: [[8]],
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column'
|
||||||
|
},
|
||||||
|
secondRow: {
|
||||||
|
extend: p,
|
||||||
|
display: 'flex',
|
||||||
|
padding: [[8]],
|
||||||
|
'& > div': {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
'& > div': {
|
||||||
|
width: 144,
|
||||||
|
height: 37,
|
||||||
|
marginBottom: 15,
|
||||||
|
marginRight: 55
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -70,12 +70,7 @@ const TransactionsList = ({ customer, data, loading, locale }) => {
|
||||||
|
|
||||||
const tableElements = [
|
const tableElements = [
|
||||||
{
|
{
|
||||||
header: 'Machine',
|
width: 40,
|
||||||
width: 160,
|
|
||||||
view: R.path(['machineName'])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
width: 125,
|
|
||||||
view: it => (
|
view: it => (
|
||||||
<>
|
<>
|
||||||
{it.txClass === 'cashOut' ? (
|
{it.txClass === 'cashOut' ? (
|
||||||
|
|
@ -86,6 +81,11 @@ const TransactionsList = ({ customer, data, loading, locale }) => {
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: 'Machine',
|
||||||
|
width: 160,
|
||||||
|
view: R.path(['machineName'])
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: 'Transaction ID',
|
header: 'Transaction ID',
|
||||||
width: 145,
|
width: 145,
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ import CustomerSidebar from './CustomerSidebar'
|
||||||
import EditableCard from './EditableCard'
|
import EditableCard from './EditableCard'
|
||||||
import Field from './Field'
|
import Field from './Field'
|
||||||
import IdDataCard from './IdDataCard'
|
import IdDataCard from './IdDataCard'
|
||||||
|
import PhotosCarousel from './PhotosCarousel'
|
||||||
import TransactionsList from './TransactionsList'
|
import TransactionsList from './TransactionsList'
|
||||||
import Upload from './Upload'
|
import Upload from './Upload'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
PhotosCarousel,
|
||||||
CustomerDetails,
|
CustomerDetails,
|
||||||
IdDataCard,
|
IdDataCard,
|
||||||
TransactionsList,
|
TransactionsList,
|
||||||
|
|
|
||||||
|
|
@ -209,10 +209,41 @@ const entryType = {
|
||||||
initialValues: { entryType: '' }
|
initialValues: { entryType: '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mapKeys = pair => {
|
||||||
|
const [key, value] = pair
|
||||||
|
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
|
||||||
|
return ['path', value]
|
||||||
|
}
|
||||||
|
if (key === 'txCustomerPhotoAt' || key === 'frontCameraAt') {
|
||||||
|
return ['date', value]
|
||||||
|
}
|
||||||
|
return pair
|
||||||
|
}
|
||||||
|
|
||||||
|
const addPhotoDir = R.map(it => {
|
||||||
|
const hasFrontCameraData = R.has('id')(it)
|
||||||
|
return hasFrontCameraData
|
||||||
|
? { ...it, photoDir: 'operator-data/customersphotos' }
|
||||||
|
: { ...it, photoDir: 'front-camera-photo' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const standardizeKeys = R.map(R.compose(R.fromPairs, R.map(mapKeys), R.toPairs))
|
||||||
|
|
||||||
|
const filterByPhotoAvailable = R.filter(
|
||||||
|
tx => !R.isNil(tx.date) && !R.isNil(tx.path)
|
||||||
|
)
|
||||||
|
|
||||||
|
const formatPhotosData = R.compose(
|
||||||
|
filterByPhotoAvailable,
|
||||||
|
addPhotoDir,
|
||||||
|
standardizeKeys
|
||||||
|
)
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getAuthorizedStatus,
|
getAuthorizedStatus,
|
||||||
getFormattedPhone,
|
getFormattedPhone,
|
||||||
getName,
|
getName,
|
||||||
entryType,
|
entryType,
|
||||||
customElements
|
customElements,
|
||||||
|
formatPhotosData
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import { useHistory } from 'react-router-dom'
|
||||||
|
|
||||||
import { P } from 'src/components/typography/index'
|
import { P } from 'src/components/typography/index'
|
||||||
import { ReactComponent as Wrench } from 'src/styling/icons/action/wrench/zodiac.svg'
|
import { ReactComponent as Wrench } from 'src/styling/icons/action/wrench/zodiac.svg'
|
||||||
import { ReactComponent as LinkIcon } from 'src/styling/icons/button/link/zodiac.svg'
|
|
||||||
import { ReactComponent as CashBoxEmpty } from 'src/styling/icons/cassettes/cashbox-empty.svg'
|
import { ReactComponent as CashBoxEmpty } from 'src/styling/icons/cassettes/cashbox-empty.svg'
|
||||||
|
import { ReactComponent as AlertLinkIcon } from 'src/styling/icons/month arrows/right.svg'
|
||||||
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/tomato.svg'
|
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/tomato.svg'
|
||||||
|
|
||||||
import styles from './Alerts.styles'
|
import styles from './Alerts.styles'
|
||||||
|
|
@ -49,7 +49,7 @@ const AlertsTable = ({ numToRender, alerts, machines }) => {
|
||||||
<Wrench style={{ height: 23, width: 23, marginRight: 8 }} />
|
<Wrench style={{ height: 23, width: 23, marginRight: 8 }} />
|
||||||
)}
|
)}
|
||||||
<P className={classes.listItemText}>{alertMessage(alert)}</P>
|
<P className={classes.listItemText}>{alertMessage(alert)}</P>
|
||||||
<LinkIcon
|
<AlertLinkIcon
|
||||||
className={classes.linkIcon}
|
className={classes.linkIcon}
|
||||||
onClick={() => history.push(links[alert.type] || '/dashboard')}
|
onClick={() => history.push(links[alert.type] || '/dashboard')}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { useQuery } from '@apollo/react-hooks'
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import Grid from '@material-ui/core/Grid'
|
import Grid from '@material-ui/core/Grid'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import classnames from 'classnames'
|
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
@ -13,6 +12,7 @@ import { H1, Info2, TL2, Label1 } from 'src/components/typography'
|
||||||
import AddMachine from 'src/pages/AddMachine'
|
import AddMachine from 'src/pages/AddMachine'
|
||||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
|
import { errorColor } from 'src/styling/variables'
|
||||||
|
|
||||||
import styles from './Dashboard.styles'
|
import styles from './Dashboard.styles'
|
||||||
import Footer from './Footer'
|
import Footer from './Footer'
|
||||||
|
|
@ -46,20 +46,20 @@ const Dashboard = () => {
|
||||||
<>
|
<>
|
||||||
<TitleSection title="Dashboard">
|
<TitleSection title="Dashboard">
|
||||||
<div className={classes.headerLabels}>
|
<div className={classes.headerLabels}>
|
||||||
<>
|
<div>
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
classes.headerLabelContainer,
|
|
||||||
classes.headerLabelContainerMargin
|
|
||||||
)}>
|
|
||||||
<TxOutIcon />
|
|
||||||
<span className={classes.headerLabelSpan}>Cash-out</span>
|
|
||||||
</div>
|
|
||||||
<div className={classes.headerLabelContainer}>
|
|
||||||
<TxInIcon />
|
<TxInIcon />
|
||||||
<span className={classes.headerLabelSpan}>Cash-in</span>
|
<span>Cash-in</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TxOutIcon />
|
||||||
|
<span>Cash-out</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<svg width={12} height={12}>
|
||||||
|
<rect width={12} height={12} rx={3} fill={errorColor} />
|
||||||
|
</svg>
|
||||||
|
<span>Action Required</span>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
</div>
|
</div>
|
||||||
</TitleSection>
|
</TitleSection>
|
||||||
<div className={classes.root}>
|
<div className={classes.root}>
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,26 @@ const { label1 } = typographyStyles
|
||||||
const styles = {
|
const styles = {
|
||||||
headerLabels: {
|
headerLabels: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row'
|
flexDirection: 'row',
|
||||||
},
|
'& > div:first-child': {
|
||||||
headerLabelContainerMargin: {
|
|
||||||
marginRight: 24
|
|
||||||
},
|
|
||||||
headerLabelContainer: {
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center',
|
||||||
|
marginLeft: 0
|
||||||
},
|
},
|
||||||
headerLabelSpan: {
|
'& > div': {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 25
|
||||||
|
},
|
||||||
|
'& > div:last-child': {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 64
|
||||||
|
},
|
||||||
|
'& > div > span': {
|
||||||
extend: label1,
|
extend: label1,
|
||||||
marginLeft: 6
|
marginLeft: 7
|
||||||
|
}
|
||||||
},
|
},
|
||||||
root: {
|
root: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
import { useQuery } from '@apollo/react-hooks'
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
import Grid from '@material-ui/core/Grid'
|
import Grid from '@material-ui/core/Grid'
|
||||||
|
|
@ -27,19 +28,15 @@ const GET_DATA = gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP })
|
BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP })
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
const Footer = () => {
|
const Footer = () => {
|
||||||
const { data } = useQuery(GET_DATA)
|
const { data } = useQuery(GET_DATA)
|
||||||
const [expanded, setExpanded] = useState(false)
|
|
||||||
const [delayedExpand, setDelayedExpand] = useState(null)
|
|
||||||
|
|
||||||
const withCommissions = R.path(['cryptoRates', 'withCommissions'])(data) ?? {}
|
const withCommissions = R.path(['cryptoRates', 'withCommissions'])(data) ?? {}
|
||||||
const classes = useStyles({
|
const classes = useStyles()
|
||||||
bigFooter: R.keys(withCommissions).length > 8,
|
|
||||||
expanded
|
|
||||||
})
|
|
||||||
const config = R.path(['config'])(data) ?? {}
|
const config = R.path(['config'])(data) ?? {}
|
||||||
const canExpand = R.keys(withCommissions).length > 4
|
const canExpand = R.keys(withCommissions).length > 4
|
||||||
|
|
||||||
|
|
@ -99,31 +96,16 @@ const Footer = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
setDelayedExpand(setTimeout(() => canExpand && setExpanded(true), 300))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
clearTimeout(delayedExpand)
|
|
||||||
setExpanded(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={classes.footer1}>
|
||||||
<div
|
<div className={classes.content1}>
|
||||||
className={classes.mouseWatcher}
|
<Grid container>
|
||||||
onMouseLeave={handleMouseLeave}
|
<Grid container className={classes.footerContainer1}>
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
/>
|
|
||||||
<div className={classes.content}>
|
|
||||||
<Grid container spacing={1}>
|
|
||||||
<Grid container className={classes.footerContainer}>
|
|
||||||
{R.keys(withCommissions).map(key => renderFooterItem(key))}
|
{R.keys(withCommissions).map(key => renderFooterItem(key))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.footer} />
|
</div>
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,52 +17,34 @@ const styles = {
|
||||||
txOutMargin: {
|
txOutMargin: {
|
||||||
marginLeft: spacer * 3
|
marginLeft: spacer * 3
|
||||||
},
|
},
|
||||||
footer: ({ expanded, bigFooter }) => ({
|
tickerLabel: {
|
||||||
height:
|
color: offColor,
|
||||||
expanded && bigFooter
|
marginTop: -5
|
||||||
? spacer * 12 * 3 + spacer * 3
|
},
|
||||||
: expanded
|
footer1: {
|
||||||
? spacer * 12 * 2 + spacer * 2
|
|
||||||
: spacer * 12,
|
|
||||||
left: 0,
|
left: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
width: '100vw',
|
width: '100vw',
|
||||||
backgroundColor: white,
|
backgroundColor: white,
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
boxShadow: '0px -1px 10px 0px rgba(50, 50, 50, 0.1)'
|
|
||||||
}),
|
|
||||||
tickerLabel: {
|
|
||||||
color: offColor,
|
|
||||||
marginTop: -5
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
width: 1200,
|
|
||||||
backgroundColor: white,
|
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
position: 'fixed',
|
boxShadow: '0px -1px 10px 0px rgba(50, 50, 50, 0.1)',
|
||||||
bottom: -spacer,
|
minHeight: spacer * 12,
|
||||||
transform: 'translateY(-100%)'
|
transition: 'min-height 0.5s ease-out',
|
||||||
|
'&:hover': {
|
||||||
|
transition: 'min-height 0.5s ease-in',
|
||||||
|
minHeight: 200
|
||||||
|
}
|
||||||
},
|
},
|
||||||
footerContainer: ({ expanded, bigFooter }) => ({
|
content1: {
|
||||||
marginLeft: spacer * 5,
|
width: 1200,
|
||||||
height: 100,
|
maxHeight: 100,
|
||||||
marginTop: expanded && bigFooter ? -300 : expanded ? -200 : -100,
|
backgroundColor: white,
|
||||||
overflow: !expanded && 'hidden'
|
zIndex: 2,
|
||||||
}),
|
bottom: -spacer,
|
||||||
mouseWatcher: ({ expanded, bigFooter }) => ({
|
margin: '0 auto'
|
||||||
position: 'fixed',
|
}
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100vw',
|
|
||||||
height:
|
|
||||||
expanded && bigFooter
|
|
||||||
? spacer * 12 * 3 + spacer * 3
|
|
||||||
: expanded
|
|
||||||
? spacer * 12 * 2 + spacer * 2
|
|
||||||
: spacer * 12,
|
|
||||||
zIndex: 2
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default styles
|
export default styles
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ import { java, neon, white } from 'src/styling/variables'
|
||||||
const styles = {
|
const styles = {
|
||||||
wrapper: {
|
wrapper: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
height: 130,
|
height: 142
|
||||||
marginTop: -8
|
|
||||||
},
|
},
|
||||||
percentageBox: {
|
percentageBox: {
|
||||||
height: 130,
|
height: 142,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
|
@ -33,11 +32,11 @@ const styles = {
|
||||||
borderRadius: 2
|
borderRadius: 2
|
||||||
},
|
},
|
||||||
inWidth: {
|
inWidth: {
|
||||||
width: value => `${value}%`
|
width: value => `${value}%`,
|
||||||
|
marginRight: value => (value === 100 ? 0 : 4)
|
||||||
},
|
},
|
||||||
outWidth: {
|
outWidth: {
|
||||||
width: value => `${100 - value}%`,
|
width: value => `${100 - value}%`
|
||||||
marginRight: 4
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,14 +58,6 @@ const PercentageChart = ({ cashIn, cashOut }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.wrapper}>
|
<div className={classes.wrapper}>
|
||||||
<div
|
|
||||||
className={classnames(
|
|
||||||
percentageClasses,
|
|
||||||
classes.outColor,
|
|
||||||
classes.outWidth
|
|
||||||
)}>
|
|
||||||
{buildPercentageView(100 - value, 'cashOut')}
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className={classnames(
|
className={classnames(
|
||||||
percentageClasses,
|
percentageClasses,
|
||||||
|
|
@ -75,6 +66,14 @@ const PercentageChart = ({ cashIn, cashOut }) => {
|
||||||
)}>
|
)}>
|
||||||
{buildPercentageView(value, 'cashIn')}
|
{buildPercentageView(value, 'cashIn')}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className={classnames(
|
||||||
|
percentageClasses,
|
||||||
|
classes.outColor,
|
||||||
|
classes.outWidth
|
||||||
|
)}>
|
||||||
|
{buildPercentageView(100 - value, 'cashOut')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const RefLineChart = ({
|
||||||
const svg = d3.select(svgRef.current)
|
const svg = d3.select(svgRef.current)
|
||||||
const margin = { top: 0, right: 0, bottom: 0, left: 0 }
|
const margin = { top: 0, right: 0, bottom: 0, left: 0 }
|
||||||
const width = 336 - margin.left - margin.right
|
const width = 336 - margin.left - margin.right
|
||||||
const height = 128 - margin.top - margin.bottom
|
const height = 140 - margin.top - margin.bottom
|
||||||
|
|
||||||
const massageData = () => {
|
const massageData = () => {
|
||||||
// if we're viewing transactions for the past day, then we group by hour. If not, we group by day
|
// if we're viewing transactions for the past day, then we group by hour. If not, we group by day
|
||||||
|
|
@ -148,7 +148,7 @@ const RefLineChart = ({
|
||||||
const y = d3
|
const y = d3
|
||||||
.scaleLinear()
|
.scaleLinear()
|
||||||
// 30 is a margin so that the labels and the percentage change label can fit and not overlay the line path
|
// 30 is a margin so that the labels and the percentage change label can fit and not overlay the line path
|
||||||
.range([height, 30])
|
.range([height, 40])
|
||||||
.domain([0, yDomain[1]])
|
.domain([0, yDomain[1]])
|
||||||
const x = d3
|
const x = d3
|
||||||
.scaleTime()
|
.scaleTime()
|
||||||
|
|
|
||||||
|
|
@ -1,197 +1,357 @@
|
||||||
|
import BigNumber from 'bignumber.js'
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
import { add } from 'date-fns/fp'
|
import { getTimezoneOffset } from 'date-fns-tz'
|
||||||
import React, { useEffect, useRef, useCallback } from 'react'
|
import { add, format, startOfWeek, startOfYear } from 'date-fns/fp'
|
||||||
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
|
|
||||||
import { backgroundColor, java, neon } from 'src/styling/variables'
|
import {
|
||||||
import { formatDate, toUtc } from 'src/utils/timezones'
|
java,
|
||||||
|
neon,
|
||||||
|
subheaderDarkColor,
|
||||||
|
offColor,
|
||||||
|
fontSecondary,
|
||||||
|
backgroundColor
|
||||||
|
} from 'src/styling/variables'
|
||||||
|
import { MINUTE, DAY, WEEK, MONTH } from 'src/utils/time'
|
||||||
|
|
||||||
const RefScatterplot = ({ data: realData, timeFrame, timezone }) => {
|
const Graph = ({ data, timeFrame, timezone }) => {
|
||||||
const svgRef = useRef()
|
const ref = useRef(null)
|
||||||
const drawGraph = useCallback(() => {
|
|
||||||
const svg = d3.select(svgRef.current)
|
const GRAPH_HEIGHT = 250
|
||||||
const margin = { top: 25, right: 0, bottom: 25, left: 15 }
|
const GRAPH_WIDTH = 555
|
||||||
const width = 555 - margin.left - margin.right
|
const GRAPH_MARGIN = useMemo(
|
||||||
const height = 150 - margin.top - margin.bottom
|
() => ({
|
||||||
// finds maximum value for the Y axis. Minimum value is 100. If value is multiple of 1000, add 100
|
top: 20,
|
||||||
// (this is because the Y axis looks best with multiples of 100)
|
right: 0.5,
|
||||||
const findMaxY = () => {
|
bottom: 27,
|
||||||
if (realData.length === 0) return 100
|
left: 43.5
|
||||||
const maxvalueTx =
|
}),
|
||||||
100 * Math.ceil(d3.max(realData, t => parseFloat(t.fiat)) / 100)
|
[]
|
||||||
const maxY = Math.max(100, maxvalueTx)
|
)
|
||||||
if (maxY % 1000 === 0) return maxY + 100
|
|
||||||
return maxY
|
const offset = getTimezoneOffset(timezone)
|
||||||
|
const NOW = Date.now() + offset
|
||||||
|
|
||||||
|
const periodDomains = {
|
||||||
|
Day: [NOW - DAY, NOW],
|
||||||
|
Week: [NOW - WEEK, NOW],
|
||||||
|
Month: [NOW - MONTH, NOW]
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeFormat = v => {
|
const dataPoints = useMemo(
|
||||||
switch (timeFrame) {
|
() => ({
|
||||||
case 'Week':
|
Day: {
|
||||||
return d3.timeFormat('%a %d')(v)
|
freq: 24,
|
||||||
case 'Month':
|
step: 60 * 60 * 1000,
|
||||||
return d3.timeFormat('%b %d')(v)
|
tick: d3.utcHour.every(4),
|
||||||
default:
|
labelFormat: '%H:%M'
|
||||||
return formatDate(v, timezone, 'HH:mm')
|
},
|
||||||
}
|
Week: {
|
||||||
|
freq: 7,
|
||||||
|
step: 24 * 60 * 60 * 1000,
|
||||||
|
tick: d3.utcDay.every(1),
|
||||||
|
labelFormat: '%a %d'
|
||||||
|
},
|
||||||
|
Month: {
|
||||||
|
freq: 30,
|
||||||
|
step: 24 * 60 * 60 * 1000,
|
||||||
|
tick: d3.utcDay.every(2),
|
||||||
|
labelFormat: '%d'
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const filterDay = useMemo(
|
||||||
|
x => (timeFrame === 'day' ? x.getUTCHours() === 0 : x.getUTCDate() === 1),
|
||||||
|
[timeFrame]
|
||||||
|
)
|
||||||
|
|
||||||
|
const getPastAndCurrentDayLabels = useCallback(d => {
|
||||||
|
const currentDate = new Date(d)
|
||||||
|
const currentDateDay = currentDate.getUTCDate()
|
||||||
|
const currentDateWeekday = currentDate.getUTCDay()
|
||||||
|
const currentDateMonth = currentDate.getUTCMonth()
|
||||||
|
|
||||||
|
const previousDate = new Date(currentDate.getTime())
|
||||||
|
previousDate.setUTCDate(currentDateDay - 1)
|
||||||
|
|
||||||
|
const previousDateDay = previousDate.getUTCDate()
|
||||||
|
const previousDateWeekday = previousDate.getUTCDay()
|
||||||
|
const previousDateMonth = previousDate.getUTCMonth()
|
||||||
|
|
||||||
|
const daysOfWeek = Array.from(Array(7)).map((_, i) =>
|
||||||
|
format('EEE', add({ days: i }, startOfWeek(new Date())))
|
||||||
|
)
|
||||||
|
|
||||||
|
const months = Array.from(Array(12)).map((_, i) =>
|
||||||
|
format('LLL', add({ months: i }, startOfYear(new Date())))
|
||||||
|
)
|
||||||
|
|
||||||
// changes values of arguments in some d3 function calls to make the graph labels look good according to the selected time frame
|
|
||||||
const findXAxisSettings = () => {
|
|
||||||
switch (timeFrame) {
|
|
||||||
case 'Week':
|
|
||||||
return {
|
return {
|
||||||
nice: 7,
|
previous:
|
||||||
ticks: 7,
|
currentDateMonth !== previousDateMonth
|
||||||
subtractDays: 7,
|
? months[previousDateMonth]
|
||||||
timeRange: [50, 500]
|
: `${daysOfWeek[previousDateWeekday]} ${previousDateDay}`,
|
||||||
}
|
current:
|
||||||
case 'Month':
|
currentDateMonth !== previousDateMonth
|
||||||
return {
|
? months[currentDateMonth]
|
||||||
nice: 6,
|
: `${daysOfWeek[currentDateWeekday]} ${currentDateDay}`
|
||||||
ticks: 6,
|
|
||||||
subtractDays: 30,
|
|
||||||
timeRange: [50, 500]
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
nice: null,
|
|
||||||
ticks: 4,
|
|
||||||
subtractDays: 1,
|
|
||||||
timeRange: [50, 500]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const buildTicks = useCallback(
|
||||||
|
domain => {
|
||||||
|
const points = []
|
||||||
|
|
||||||
|
const roundDate = d => {
|
||||||
|
const step = dataPoints[timeFrame].step
|
||||||
|
return new Date(Math.ceil(d.valueOf() / step) * step)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sets width of the graph
|
for (let i = 0; i <= dataPoints[timeFrame].freq; i++) {
|
||||||
svg.attr('width', width)
|
const stepDate = new Date(NOW - i * dataPoints[timeFrame].step)
|
||||||
|
if (roundDate(stepDate) > domain[1]) continue
|
||||||
|
if (stepDate < domain[0]) continue
|
||||||
|
points.push(roundDate(stepDate))
|
||||||
|
}
|
||||||
|
|
||||||
// background color for the graph
|
return points
|
||||||
svg
|
},
|
||||||
.append('rect')
|
[NOW, dataPoints, timeFrame]
|
||||||
.attr('x', 0)
|
)
|
||||||
.attr('y', 0)
|
|
||||||
.attr('width', width)
|
|
||||||
.attr('height', height + margin.top)
|
|
||||||
.attr('fill', backgroundColor)
|
|
||||||
|
|
||||||
// declare g variable where more svg components will be attached
|
const x = d3
|
||||||
const g = svg
|
.scaleUtc()
|
||||||
.append('g')
|
.domain(periodDomains[timeFrame])
|
||||||
.attr('transform', `translate(${margin.left},${margin.top})`)
|
.range([GRAPH_MARGIN.left, GRAPH_WIDTH - GRAPH_MARGIN.right])
|
||||||
|
|
||||||
// y axis range: round up to 100 highest data value, if rounds up to 1000, add 100.
|
|
||||||
// this keeps the vertical axis nice looking
|
|
||||||
const maxY = findMaxY()
|
|
||||||
const xAxisSettings = findXAxisSettings()
|
|
||||||
|
|
||||||
// y and x scales
|
|
||||||
const y = d3
|
const y = d3
|
||||||
.scaleLinear()
|
.scaleLinear()
|
||||||
.range([height, 0])
|
|
||||||
.domain([0, maxY])
|
|
||||||
.nice(3)
|
|
||||||
const x = d3
|
|
||||||
.scaleTime()
|
|
||||||
.domain([
|
.domain([
|
||||||
add({ days: -xAxisSettings.subtractDays }, new Date()).valueOf(),
|
0,
|
||||||
new Date().valueOf()
|
(d3.max(data, d => new BigNumber(d.fiat).toNumber()) ?? 1000) * 1.05
|
||||||
])
|
])
|
||||||
.range(xAxisSettings.timeRange)
|
.nice()
|
||||||
.nice(xAxisSettings.nice)
|
.range([GRAPH_HEIGHT - GRAPH_MARGIN.bottom, GRAPH_MARGIN.top])
|
||||||
|
|
||||||
const timeValue = s => {
|
const buildBackground = useCallback(
|
||||||
const date = toUtc(s)
|
g => {
|
||||||
return x(date.valueOf())
|
g.append('rect')
|
||||||
}
|
.attr('x', 0)
|
||||||
|
.attr('y', GRAPH_MARGIN.top)
|
||||||
// horizontal gridlines
|
.attr('width', GRAPH_WIDTH)
|
||||||
const makeYGridlines = () => {
|
.attr('height', GRAPH_HEIGHT - GRAPH_MARGIN.top - GRAPH_MARGIN.bottom)
|
||||||
return d3.axisLeft(y).ticks(4)
|
.attr('fill', backgroundColor)
|
||||||
}
|
},
|
||||||
g.append('g')
|
[GRAPH_MARGIN]
|
||||||
.style('color', '#eef1ff')
|
|
||||||
.call(
|
|
||||||
makeYGridlines()
|
|
||||||
.tickSize(-width)
|
|
||||||
.tickFormat('')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const buildXAxis = useCallback(
|
||||||
|
g =>
|
||||||
|
g
|
||||||
|
.attr(
|
||||||
|
'transform',
|
||||||
|
`translate(0, ${GRAPH_HEIGHT - GRAPH_MARGIN.bottom})`
|
||||||
|
)
|
||||||
|
.call(
|
||||||
|
d3
|
||||||
|
.axisBottom(x)
|
||||||
|
.ticks(dataPoints[timeFrame].tick)
|
||||||
|
.tickFormat(d => {
|
||||||
|
return d3.timeFormat(dataPoints[timeFrame].labelFormat)(
|
||||||
|
d.getTime() + d.getTimezoneOffset() * MINUTE
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.call(g => g.select('.domain').remove()),
|
||||||
|
[GRAPH_MARGIN, dataPoints, timeFrame, x]
|
||||||
|
)
|
||||||
|
|
||||||
|
const buildYAxis = useCallback(
|
||||||
|
g =>
|
||||||
|
g
|
||||||
|
.attr('transform', `translate(${GRAPH_MARGIN.left}, 0)`)
|
||||||
|
.call(d3.axisLeft(y).ticks(5))
|
||||||
.call(g => g.select('.domain').remove())
|
.call(g => g.select('.domain').remove())
|
||||||
|
|
||||||
/* X AXIS */
|
|
||||||
// this one is for the labels at the bottom
|
|
||||||
g.append('g')
|
|
||||||
.attr('transform', 'translate(0,' + height + ')')
|
|
||||||
.style('font-size', '13px')
|
|
||||||
.style('color', '#5f668a')
|
|
||||||
.style('font-family', 'MuseoSans')
|
|
||||||
.style('margin-top', '11px')
|
|
||||||
.call(
|
|
||||||
d3
|
|
||||||
.axisBottom(x)
|
|
||||||
.ticks(xAxisSettings.ticks)
|
|
||||||
.tickSize(0)
|
|
||||||
.tickFormat(timeFormat)
|
|
||||||
)
|
|
||||||
.selectAll('text')
|
.selectAll('text')
|
||||||
.attr('dy', '1.5em')
|
.attr('dy', '-0.25rem'),
|
||||||
// this is for the x axis line. It is the same color as the horizontal grid lines
|
[GRAPH_MARGIN, y]
|
||||||
g.append('g')
|
|
||||||
.attr('transform', 'translate(0,' + height + ')')
|
|
||||||
.style('color', '#eef1ff')
|
|
||||||
.call(
|
|
||||||
d3
|
|
||||||
.axisBottom(x)
|
|
||||||
.ticks(6)
|
|
||||||
.tickSize(0)
|
|
||||||
.tickFormat('')
|
|
||||||
)
|
)
|
||||||
.selectAll('text')
|
|
||||||
.attr('dy', '1.5em')
|
|
||||||
|
|
||||||
// Y axis
|
const buildGrid = useCallback(
|
||||||
g.append('g')
|
g => {
|
||||||
.style('font-size', '13px')
|
g.attr('stroke', subheaderDarkColor)
|
||||||
.style('color', '#5f668a')
|
.attr('fill', subheaderDarkColor)
|
||||||
.style('font-family', 'MuseoSans')
|
// Vertical lines
|
||||||
.style('margin-top', '11px')
|
.call(g =>
|
||||||
.call(
|
g
|
||||||
|
.append('g')
|
||||||
|
.selectAll('line')
|
||||||
|
.data(buildTicks(x.domain()))
|
||||||
|
.join('line')
|
||||||
|
.attr('x1', d => 0.5 + x(d))
|
||||||
|
.attr('x2', d => 0.5 + x(d))
|
||||||
|
.attr('y1', GRAPH_MARGIN.top)
|
||||||
|
.attr('y2', GRAPH_HEIGHT - GRAPH_MARGIN.bottom)
|
||||||
|
.attr('stroke-width', 1)
|
||||||
|
)
|
||||||
|
// Horizontal lines
|
||||||
|
.call(g =>
|
||||||
|
g
|
||||||
|
.append('g')
|
||||||
|
.selectAll('line')
|
||||||
|
.data(
|
||||||
d3
|
d3
|
||||||
.axisLeft(y)
|
.axisLeft(y)
|
||||||
.ticks(4)
|
.scale()
|
||||||
.tickSize(0)
|
.ticks(5)
|
||||||
)
|
)
|
||||||
.call(g => g.select('.domain').remove())
|
.join('line')
|
||||||
.selectAll('text')
|
.attr('y1', d => 0.5 + y(d))
|
||||||
.attr('dy', '-0.40em')
|
.attr('y2', d => 0.5 + y(d))
|
||||||
.attr('dx', '3em')
|
.attr('x1', GRAPH_MARGIN.left)
|
||||||
|
.attr('x2', GRAPH_WIDTH - GRAPH_MARGIN.right)
|
||||||
// Append dots
|
)
|
||||||
const dots = svg
|
// Thick vertical lines
|
||||||
|
.call(g =>
|
||||||
|
g
|
||||||
.append('g')
|
.append('g')
|
||||||
.attr('transform', `translate(${margin.left},${margin.top})`)
|
.selectAll('line')
|
||||||
|
.data(buildTicks(x.domain()).filter(filterDay))
|
||||||
|
.join('line')
|
||||||
|
.attr('class', 'dateSeparator')
|
||||||
|
.attr('x1', d => 0.5 + x(d))
|
||||||
|
.attr('x2', d => 0.5 + x(d))
|
||||||
|
.attr('y1', GRAPH_MARGIN.top - 10)
|
||||||
|
.attr('y2', GRAPH_HEIGHT - GRAPH_MARGIN.bottom)
|
||||||
|
.attr('stroke-width', 2)
|
||||||
|
.join('text')
|
||||||
|
)
|
||||||
|
// Left side breakpoint label
|
||||||
|
.call(g => {
|
||||||
|
const separator = d3
|
||||||
|
?.select('.dateSeparator')
|
||||||
|
?.node()
|
||||||
|
?.getBBox()
|
||||||
|
|
||||||
dots
|
if (!separator) return
|
||||||
.selectAll('circle')
|
|
||||||
.data(realData)
|
const breakpoint = buildTicks(x.domain()).filter(filterDay)
|
||||||
.enter()
|
|
||||||
.append('circle')
|
const labels = getPastAndCurrentDayLabels(breakpoint)
|
||||||
.attr('cx', d => timeValue(d.created))
|
|
||||||
.attr('cy', d => y(d.fiat))
|
return g
|
||||||
.attr('r', 4)
|
.append('text')
|
||||||
.style('fill', d => (d.txClass === 'cashIn' ? java : neon))
|
.attr('x', separator.x - 7)
|
||||||
}, [realData, timeFrame, timezone])
|
.attr('y', separator.y)
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.attr('dy', '.25em')
|
||||||
|
.text(labels.previous)
|
||||||
|
})
|
||||||
|
// Right side breakpoint label
|
||||||
|
.call(g => {
|
||||||
|
const separator = d3
|
||||||
|
?.select('.dateSeparator')
|
||||||
|
?.node()
|
||||||
|
?.getBBox()
|
||||||
|
|
||||||
|
if (!separator) return
|
||||||
|
|
||||||
|
const breakpoint = buildTicks(x.domain()).filter(filterDay)
|
||||||
|
|
||||||
|
const labels = getPastAndCurrentDayLabels(breakpoint)
|
||||||
|
|
||||||
|
return g
|
||||||
|
.append('text')
|
||||||
|
.attr('x', separator.x + 7)
|
||||||
|
.attr('y', separator.y)
|
||||||
|
.attr('text-anchor', 'start')
|
||||||
|
.attr('dy', '.25em')
|
||||||
|
.text(labels.current)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[GRAPH_MARGIN, buildTicks, getPastAndCurrentDayLabels, x, y, filterDay]
|
||||||
|
)
|
||||||
|
|
||||||
|
const formatTicksText = useCallback(
|
||||||
|
() =>
|
||||||
|
d3
|
||||||
|
.selectAll('.tick text')
|
||||||
|
.style('stroke', offColor)
|
||||||
|
.style('fill', offColor)
|
||||||
|
.style('stroke-width', 0)
|
||||||
|
.style('font-family', fontSecondary),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const formatText = useCallback(
|
||||||
|
() =>
|
||||||
|
d3
|
||||||
|
.selectAll('text')
|
||||||
|
.style('stroke', offColor)
|
||||||
|
.style('fill', offColor)
|
||||||
|
.style('stroke-width', 0)
|
||||||
|
.style('font-family', fontSecondary),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const formatTicks = useCallback(() => {
|
||||||
|
d3.selectAll('.tick line')
|
||||||
|
.style('stroke', 'transparent')
|
||||||
|
.style('fill', 'transparent')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const drawData = useCallback(
|
||||||
|
g => {
|
||||||
|
g.selectAll('circle')
|
||||||
|
.data(data)
|
||||||
|
.join('circle')
|
||||||
|
.attr('cx', d => {
|
||||||
|
const created = new Date(d.created)
|
||||||
|
return x(created.setTime(created.getTime() + offset))
|
||||||
|
})
|
||||||
|
.attr('cy', d => y(new BigNumber(d.fiat).toNumber()))
|
||||||
|
.attr('fill', d => (d.txClass === 'cashIn' ? java : neon))
|
||||||
|
.attr('r', 3.5)
|
||||||
|
},
|
||||||
|
[data, offset, x, y]
|
||||||
|
)
|
||||||
|
|
||||||
|
const drawChart = useCallback(() => {
|
||||||
|
const svg = d3
|
||||||
|
.select(ref.current)
|
||||||
|
.attr('viewBox', [0, 0, GRAPH_WIDTH, GRAPH_HEIGHT])
|
||||||
|
|
||||||
|
svg.append('g').call(buildBackground)
|
||||||
|
svg.append('g').call(buildGrid)
|
||||||
|
svg.append('g').call(buildXAxis)
|
||||||
|
svg.append('g').call(buildYAxis)
|
||||||
|
svg.append('g').call(formatTicksText)
|
||||||
|
svg.append('g').call(formatText)
|
||||||
|
svg.append('g').call(formatTicks)
|
||||||
|
svg.append('g').call(drawData)
|
||||||
|
|
||||||
|
return svg.node()
|
||||||
|
}, [
|
||||||
|
buildBackground,
|
||||||
|
buildGrid,
|
||||||
|
buildXAxis,
|
||||||
|
buildYAxis,
|
||||||
|
drawData,
|
||||||
|
formatText,
|
||||||
|
formatTicks,
|
||||||
|
formatTicksText
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// first we clear old chart DOM elements on component update
|
d3.select(ref.current)
|
||||||
d3.select(svgRef.current)
|
|
||||||
.selectAll('*')
|
.selectAll('*')
|
||||||
.remove()
|
.remove()
|
||||||
drawGraph()
|
drawChart()
|
||||||
}, [drawGraph])
|
}, [drawChart])
|
||||||
|
|
||||||
return (
|
return <svg ref={ref} />
|
||||||
<>
|
|
||||||
<svg ref={svgRef} />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
export default RefScatterplot
|
|
||||||
|
export default Graph
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,13 @@ import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
import { EmptyTable } from 'src/components/table'
|
import { EmptyTable } from 'src/components/table'
|
||||||
import { Label1, Label2 } from 'src/components/typography/index'
|
import { Label1, Label2, P } from 'src/components/typography/index'
|
||||||
import { ReactComponent as PercentDownIcon } from 'src/styling/icons/dashboard/down.svg'
|
import { ReactComponent as PercentDownIcon } from 'src/styling/icons/dashboard/down.svg'
|
||||||
import { ReactComponent as PercentNeutralIcon } from 'src/styling/icons/dashboard/equal.svg'
|
import { ReactComponent as PercentNeutralIcon } from 'src/styling/icons/dashboard/equal.svg'
|
||||||
import { ReactComponent as PercentUpIcon } from 'src/styling/icons/dashboard/up.svg'
|
import { ReactComponent as PercentUpIcon } from 'src/styling/icons/dashboard/up.svg'
|
||||||
|
import { java, neon } from 'src/styling/variables'
|
||||||
import { fromNamespace } from 'src/utils/config'
|
import { fromNamespace } from 'src/utils/config'
|
||||||
|
import { timezones } from 'src/utils/timezone-list'
|
||||||
import { toTimezone } from 'src/utils/timezones'
|
import { toTimezone } from 'src/utils/timezones'
|
||||||
|
|
||||||
import PercentageChart from './Graphs/PercentageChart'
|
import PercentageChart from './Graphs/PercentageChart'
|
||||||
|
|
@ -199,9 +201,30 @@ const SystemPerformance = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* todo new customers */}
|
{/* todo new customers */}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container className={classes.gridContainer}>
|
<Grid container className={classes.txGraphContainer}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Label2>Transactions</Label2>
|
<div className={classes.graphHeader}>
|
||||||
|
<Label2 noMargin>Transactions</Label2>
|
||||||
|
<div className={classes.labelWrapper}>
|
||||||
|
<P noMargin>
|
||||||
|
{timezones[timezone].short ?? timezones[timezone].long}{' '}
|
||||||
|
timezone
|
||||||
|
</P>
|
||||||
|
<span className={classes.verticalLine} />
|
||||||
|
<div>
|
||||||
|
<svg width={8} height={8}>
|
||||||
|
<rect width={8} height={8} rx={4} fill={java} />
|
||||||
|
</svg>
|
||||||
|
<Label1 noMargin>In</Label1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<svg width={8} height={8}>
|
||||||
|
<rect width={8} height={8} rx={4} fill={neon} />
|
||||||
|
</svg>
|
||||||
|
<Label1 noMargin>Out</Label1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Scatterplot
|
<Scatterplot
|
||||||
timeFrame={selectedRange}
|
timeFrame={selectedRange}
|
||||||
data={transactionsToShow}
|
data={transactionsToShow}
|
||||||
|
|
@ -209,9 +232,9 @@ const SystemPerformance = () => {
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid container className={classes.gridContainer}>
|
<Grid container className={classes.commissionGraphContainer}>
|
||||||
<Grid item xs={8}>
|
<Grid item xs={8}>
|
||||||
<Label2 className={classes.labelMargin}>
|
<Label2 noMargin className={classes.commissionProfitTitle}>
|
||||||
Profit from commissions
|
Profit from commissions
|
||||||
</Label2>
|
</Label2>
|
||||||
<div className={classes.profitContainer}>
|
<div className={classes.profitContainer}>
|
||||||
|
|
@ -233,23 +256,22 @@ const SystemPerformance = () => {
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={4}>
|
<Grid item xs={4}>
|
||||||
<Grid container>
|
<Grid container className={classes.graphHeader}>
|
||||||
<Grid item>
|
<Label2 noMargin>Direction</Label2>
|
||||||
<Label2 className={classes.labelMargin}>Direction</Label2>
|
<div className={classes.labelWrapper}>
|
||||||
</Grid>
|
<div>
|
||||||
<Grid
|
<svg width={8} height={8}>
|
||||||
item
|
<rect width={8} height={8} rx={2} fill={java} />
|
||||||
className={classnames(
|
</svg>
|
||||||
classes.directionLabelContainer,
|
<Label1 noMargin>In</Label1>
|
||||||
classes.dirLabContMargin
|
</div>
|
||||||
)}>
|
<div>
|
||||||
<div className={classes.outSquare} />
|
<svg width={8} height={8}>
|
||||||
<Label1 className={classes.directionLabel}>Out</Label1>
|
<rect width={8} height={8} rx={2} fill={neon} />
|
||||||
</Grid>
|
</svg>
|
||||||
<Grid item className={classes.directionLabelContainer}>
|
<Label1 noMargin>Out</Label1>
|
||||||
<div className={classes.inSquare} />
|
</div>
|
||||||
<Label1 className={classes.directionLabel}>In</Label1>
|
</div>
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs>
|
<Grid item xs>
|
||||||
<PercentageChart
|
<PercentageChart
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
offColor,
|
offColor,
|
||||||
|
offDarkColor,
|
||||||
spacer,
|
spacer,
|
||||||
primaryColor,
|
primaryColor,
|
||||||
fontSize3,
|
fontSize3,
|
||||||
|
|
@ -7,8 +8,6 @@ import {
|
||||||
fontColor,
|
fontColor,
|
||||||
spring4,
|
spring4,
|
||||||
tomato,
|
tomato,
|
||||||
java,
|
|
||||||
neon,
|
|
||||||
comet
|
comet
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
|
|
@ -67,12 +66,6 @@ const styles = {
|
||||||
navContainer: {
|
navContainer: {
|
||||||
display: 'flex'
|
display: 'flex'
|
||||||
},
|
},
|
||||||
profitLabel: {
|
|
||||||
fontSize: fontSize3,
|
|
||||||
fontFamily: fontSecondary,
|
|
||||||
fontWeight: 700,
|
|
||||||
color: fontColor
|
|
||||||
},
|
|
||||||
percentUp: {
|
percentUp: {
|
||||||
fontSize: fontSize3,
|
fontSize: fontSize3,
|
||||||
fontFamily: fontSecondary,
|
fontFamily: fontSecondary,
|
||||||
|
|
@ -96,34 +89,14 @@ const styles = {
|
||||||
profitContainer: {
|
profitContainer: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
margin: '0 26px -30px 16px',
|
margin: '23px 26px -30px 16px',
|
||||||
position: 'relative'
|
position: 'relative'
|
||||||
},
|
},
|
||||||
gridContainer: {
|
profitLabel: {
|
||||||
marginTop: 30,
|
fontSize: fontSize3,
|
||||||
height: 225
|
fontFamily: fontSecondary,
|
||||||
},
|
fontWeight: 700,
|
||||||
inSquare: {
|
color: fontColor
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
borderRadius: 2,
|
|
||||||
marginTop: 18,
|
|
||||||
marginRight: 4,
|
|
||||||
backgroundColor: java
|
|
||||||
},
|
|
||||||
outSquare: {
|
|
||||||
width: 8,
|
|
||||||
height: 8,
|
|
||||||
borderRadius: 2,
|
|
||||||
marginTop: 18,
|
|
||||||
marginRight: 4,
|
|
||||||
backgroundColor: neon
|
|
||||||
},
|
|
||||||
directionLabelContainer: {
|
|
||||||
display: 'flex'
|
|
||||||
},
|
|
||||||
dirLabContMargin: {
|
|
||||||
marginRight: 20
|
|
||||||
},
|
},
|
||||||
directionIcon: {
|
directionIcon: {
|
||||||
width: 16,
|
width: 16,
|
||||||
|
|
@ -131,12 +104,50 @@ const styles = {
|
||||||
marginBottom: -2,
|
marginBottom: -2,
|
||||||
marginRight: 4
|
marginRight: 4
|
||||||
},
|
},
|
||||||
labelMargin: {
|
|
||||||
marginBottom: 20,
|
|
||||||
marginRight: 32
|
|
||||||
},
|
|
||||||
emptyTransactions: {
|
emptyTransactions: {
|
||||||
paddingTop: 40
|
paddingTop: 40
|
||||||
|
},
|
||||||
|
commissionProfitTitle: {
|
||||||
|
marginBottom: 16
|
||||||
|
},
|
||||||
|
graphHeader: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginBottom: 16
|
||||||
|
},
|
||||||
|
labelWrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
'& > div': {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 15,
|
||||||
|
'&:first-child': {
|
||||||
|
marginLeft: 0
|
||||||
|
},
|
||||||
|
'& > p': {
|
||||||
|
marginLeft: 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
txGraphContainer: {
|
||||||
|
height: 300,
|
||||||
|
marginTop: 30
|
||||||
|
},
|
||||||
|
commissionsGraphContainer: {
|
||||||
|
height: 250,
|
||||||
|
marginTop: 30
|
||||||
|
},
|
||||||
|
verticalLine: {
|
||||||
|
height: 15,
|
||||||
|
width: 1,
|
||||||
|
backgroundColor: offDarkColor,
|
||||||
|
marginLeft: 31,
|
||||||
|
marginRight: 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,9 @@ const Funding = () => {
|
||||||
<div className={classes.addressWrapper}>
|
<div className={classes.addressWrapper}>
|
||||||
<div className={classes.mono}>
|
<div className={classes.mono}>
|
||||||
<strong>
|
<strong>
|
||||||
<CopyToClipboard buttonClassname={classes.copyToClipboard}>
|
<CopyToClipboard
|
||||||
|
buttonClassname={classes.copyToClipboard}
|
||||||
|
key={selected.cryptoCode}>
|
||||||
{formatAddress(
|
{formatAddress(
|
||||||
selected.cryptoCode,
|
selected.cryptoCode,
|
||||||
selected.fundingAddress
|
selected.fundingAddress
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import * as R from 'ramda'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
|
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
|
||||||
import timezoneList from 'src/utils/timezone-list'
|
import { labels as timezoneList } from 'src/utils/timezone-list'
|
||||||
|
|
||||||
const getFields = (getData, names, onChange, auxElements = []) => {
|
const getFields = (getData, names, onChange, auxElements = []) => {
|
||||||
return R.filter(
|
return R.filter(
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import { DeleteDialog } from 'src/components/DeleteDialog'
|
||||||
import { Link, Button, IconButton } from 'src/components/buttons'
|
import { Link, Button, IconButton } from 'src/components/buttons'
|
||||||
import DataTable from 'src/components/tables/DataTable'
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
import { Label3, TL1 } from 'src/components/typography'
|
import { Label3, TL1 } from 'src/components/typography'
|
||||||
import { ReactComponent as CardIdIcon } from 'src/styling/icons/ID/card/zodiac.svg'
|
|
||||||
import { ReactComponent as PhoneIdIcon } from 'src/styling/icons/ID/phone/zodiac.svg'
|
import { ReactComponent as PhoneIdIcon } from 'src/styling/icons/ID/phone/zodiac.svg'
|
||||||
import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
|
import { ReactComponent as DeleteIcon } from 'src/styling/icons/action/delete/enabled.svg'
|
||||||
|
|
||||||
|
|
@ -49,7 +48,6 @@ const GET_CUSTOMERS = gql`
|
||||||
id
|
id
|
||||||
phone
|
phone
|
||||||
idCardData
|
idCardData
|
||||||
phone
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
@ -64,7 +62,9 @@ const IndividualDiscounts = () => {
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const toggleModal = () => setShowModal(!showModal)
|
const toggleModal = () => setShowModal(!showModal)
|
||||||
|
|
||||||
const { data: discountResponse, loading } = useQuery(GET_INDIVIDUAL_DISCOUNTS)
|
const { data: discountResponse, loading: discountLoading } = useQuery(
|
||||||
|
GET_INDIVIDUAL_DISCOUNTS
|
||||||
|
)
|
||||||
const { data: customerData, loading: customerLoading } = useQuery(
|
const { data: customerData, loading: customerLoading } = useQuery(
|
||||||
GET_CUSTOMERS
|
GET_CUSTOMERS
|
||||||
)
|
)
|
||||||
|
|
@ -102,12 +102,6 @@ const IndividualDiscounts = () => {
|
||||||
<div className={classes.identification}>
|
<div className={classes.identification}>
|
||||||
<PhoneIdIcon />
|
<PhoneIdIcon />
|
||||||
<span>{customer.phone}</span>
|
<span>{customer.phone}</span>
|
||||||
{customer?.idCardData?.documentNumber && (
|
|
||||||
<>
|
|
||||||
<CardIdIcon />
|
|
||||||
<span>{customer?.idCardData?.documentNumber}</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -160,11 +154,12 @@ const IndividualDiscounts = () => {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const isLoading = loading || customerLoading
|
const loading = discountLoading || customerLoading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isLoading && !R.isEmpty(discountResponse.individualDiscounts) && (
|
{!loading && !R.isEmpty(discountResponse.individualDiscounts) && (
|
||||||
|
<>
|
||||||
<Box
|
<Box
|
||||||
marginBottom={4}
|
marginBottom={4}
|
||||||
marginTop={-7}
|
marginTop={-7}
|
||||||
|
|
@ -175,9 +170,6 @@ const IndividualDiscounts = () => {
|
||||||
Add new code
|
Add new code
|
||||||
</Link>
|
</Link>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
{!isLoading && !R.isEmpty(discountResponse.individualDiscounts) && (
|
|
||||||
<>
|
|
||||||
<DataTable
|
<DataTable
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={R.path(['individualDiscounts'])(discountResponse)}
|
data={R.path(['individualDiscounts'])(discountResponse)}
|
||||||
|
|
@ -196,7 +188,7 @@ const IndividualDiscounts = () => {
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!isLoading && R.isEmpty(discountResponse.individualDiscounts) && (
|
{!loading && R.isEmpty(discountResponse.individualDiscounts) && (
|
||||||
<Box display="flex" alignItems="left" flexDirection="column">
|
<Box display="flex" alignItems="left" flexDirection="column">
|
||||||
<Label3>
|
<Label3>
|
||||||
It seems there are no active individual customer discounts on your
|
It seems there are no active individual customer discounts on your
|
||||||
|
|
|
||||||
|
|
@ -81,16 +81,21 @@ const Logs = () => {
|
||||||
|
|
||||||
const deviceId = selected?.deviceId
|
const deviceId = selected?.deviceId
|
||||||
|
|
||||||
const { data: machineResponse } = useQuery(GET_MACHINES)
|
const { data: machineResponse, loading: machinesLoading } = useQuery(
|
||||||
|
GET_MACHINES
|
||||||
|
)
|
||||||
|
|
||||||
const { data: configResponse } = useQuery(GET_DATA)
|
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||||
|
|
||||||
const { data: logsResponse, loading } = useQuery(GET_MACHINE_LOGS, {
|
const { data: logsResponse, loading: logsLoading } = useQuery(
|
||||||
|
GET_MACHINE_LOGS,
|
||||||
|
{
|
||||||
variables: { deviceId, limit: NUM_LOG_RESULTS },
|
variables: { deviceId, limit: NUM_LOG_RESULTS },
|
||||||
skip: !selected,
|
skip: !selected,
|
||||||
onCompleted: () => setSaveMessage('')
|
onCompleted: () => setSaveMessage('')
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if (machineResponse?.machines?.length && !selected) {
|
if (machineResponse?.machines?.length && !selected) {
|
||||||
setSelected(machineResponse?.machines[0])
|
setSelected(machineResponse?.machines[0])
|
||||||
|
|
@ -100,6 +105,8 @@ const Logs = () => {
|
||||||
return R.path(['deviceId'])(selected) === it.deviceId
|
return R.path(['deviceId'])(selected) === it.deviceId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loading = machinesLoading || configLoading || logsLoading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.titleWrapper}>
|
<div className={classes.titleWrapper}>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ const widthsByNumberOfCassettes = {
|
||||||
const ValidationSchema = Yup.object().shape({
|
const ValidationSchema = Yup.object().shape({
|
||||||
name: Yup.string().required('Required'),
|
name: Yup.string().required('Required'),
|
||||||
cashbox: Yup.number()
|
cashbox: Yup.number()
|
||||||
.label('Cashbox')
|
.label('Cash box')
|
||||||
.required()
|
.required()
|
||||||
.integer()
|
.integer()
|
||||||
.min(0)
|
.min(0)
|
||||||
|
|
@ -82,7 +82,7 @@ const SET_CASSETTE_BILLS = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const CashCassettes = ({ machine, config, refetchData }) => {
|
const CashCassettes = ({ machine, config, refetchData, bills }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const [wizard, setWizard] = useState(false)
|
const [wizard, setWizard] = useState(false)
|
||||||
|
|
@ -101,11 +101,15 @@ const CashCassettes = ({ machine, config, refetchData }) => {
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
name: 'cashbox',
|
name: 'cashbox',
|
||||||
header: 'Cashbox',
|
header: 'Cash box',
|
||||||
width: widthsByNumberOfCassettes[numberOfCassettes].cashbox,
|
width: widthsByNumberOfCassettes[numberOfCassettes].cashbox,
|
||||||
stripe: false,
|
stripe: false,
|
||||||
view: value => (
|
view: value => (
|
||||||
<CashIn currency={{ code: fiatCurrency }} notes={value} total={0} />
|
<CashIn
|
||||||
|
currency={{ code: fiatCurrency }}
|
||||||
|
notes={value}
|
||||||
|
total={R.sum(R.map(it => it.fiat)(bills))}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
input: NumberInput,
|
input: NumberInput,
|
||||||
inputProps: {
|
inputProps: {
|
||||||
|
|
@ -119,7 +123,7 @@ const CashCassettes = ({ machine, config, refetchData }) => {
|
||||||
it => {
|
it => {
|
||||||
elements.push({
|
elements.push({
|
||||||
name: `cassette${it}`,
|
name: `cassette${it}`,
|
||||||
header: `Cash-out ${it}`,
|
header: `Cash cassette ${it}`,
|
||||||
width: widthsByNumberOfCassettes[numberOfCassettes].cassette,
|
width: widthsByNumberOfCassettes[numberOfCassettes].cassette,
|
||||||
stripe: true,
|
stripe: true,
|
||||||
doubleHeader: 'Cash-out',
|
doubleHeader: 'Cash-out',
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ const Transactions = ({ id }) => {
|
||||||
const { data: configData, loading: configLoading } = useQuery(GET_DATA)
|
const { data: configData, loading: configLoading } = useQuery(GET_DATA)
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configData)
|
const timezone = R.path(['config', 'locale_timezone'], configData)
|
||||||
|
|
||||||
const loading = txLoading && configLoading
|
const loading = txLoading || configLoading
|
||||||
|
|
||||||
if (!loading && txResponse) {
|
if (!loading && txResponse) {
|
||||||
txResponse.transactions = txResponse.transactions.splice(0, 5)
|
txResponse.transactions = txResponse.transactions.splice(0, 5)
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,14 @@ const MachineRoute = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Machines = ({ data, refetch, reload, bills }) => {
|
const Machines = ({ data, refetch, reload }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const timezone = R.path(['config', 'locale_timezone'], data) ?? {}
|
const timezone = R.path(['config', 'locale_timezone'], data) ?? {}
|
||||||
|
|
||||||
const machine = R.path(['machine'])(data) ?? {}
|
const machine = R.path(['machine'])(data) ?? {}
|
||||||
const config = R.path(['config'])(data) ?? {}
|
const config = R.path(['config'])(data) ?? {}
|
||||||
|
const bills = R.path(['bills'])(data) ?? []
|
||||||
|
|
||||||
const machineName = R.path(['name'])(machine) ?? null
|
const machineName = R.path(['name'])(machine) ?? null
|
||||||
const machineID = R.path(['deviceId'])(machine) ?? null
|
const machineID = R.path(['deviceId'])(machine) ?? null
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ const useStyles = makeStyles(styles)
|
||||||
const ValidationSchema = Yup.object().shape({
|
const ValidationSchema = Yup.object().shape({
|
||||||
name: Yup.string().required(),
|
name: Yup.string().required(),
|
||||||
cashbox: Yup.number()
|
cashbox: Yup.number()
|
||||||
.label('Cashbox')
|
.label('Cash box')
|
||||||
.required()
|
.required()
|
||||||
.integer()
|
.integer()
|
||||||
.min(0)
|
.min(0)
|
||||||
|
|
@ -63,7 +63,7 @@ const ValidationSchema = Yup.object().shape({
|
||||||
})
|
})
|
||||||
|
|
||||||
const GET_MACHINES_AND_CONFIG = gql`
|
const GET_MACHINES_AND_CONFIG = gql`
|
||||||
query getData {
|
query getData($billFilters: JSONObject) {
|
||||||
machines {
|
machines {
|
||||||
name
|
name
|
||||||
id: deviceId
|
id: deviceId
|
||||||
|
|
@ -75,24 +75,21 @@ const GET_MACHINES_AND_CONFIG = gql`
|
||||||
numberOfCassettes
|
numberOfCassettes
|
||||||
}
|
}
|
||||||
config
|
config
|
||||||
|
bills(filters: $billFilters) {
|
||||||
|
id
|
||||||
|
fiat
|
||||||
|
created
|
||||||
|
deviceId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const SAVE_CONFIG = gql`
|
const SAVE_CONFIG = gql`
|
||||||
mutation Save($config: JSONObject) {
|
mutation Save($config: JSONObject) {
|
||||||
saveConfig(config: $config)
|
saveConfig(config: $config)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
/*
|
|
||||||
// for cash in total calculation
|
|
||||||
bills {
|
|
||||||
fiat
|
|
||||||
deviceId
|
|
||||||
created
|
|
||||||
cashbox
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const SET_CASSETTE_BILLS = gql`
|
const SET_CASSETTE_BILLS = gql`
|
||||||
mutation MachineAction(
|
mutation MachineAction(
|
||||||
$deviceId: ID!
|
$deviceId: ID!
|
||||||
|
|
@ -128,7 +125,13 @@ const CashCassettes = () => {
|
||||||
const [editingSchema, setEditingSchema] = useState(null)
|
const [editingSchema, setEditingSchema] = useState(null)
|
||||||
const [selectedRadio, setSelectedRadio] = useState(null)
|
const [selectedRadio, setSelectedRadio] = useState(null)
|
||||||
|
|
||||||
const { data } = useQuery(GET_MACHINES_AND_CONFIG)
|
const { data, loading: dataLoading } = useQuery(GET_MACHINES_AND_CONFIG, {
|
||||||
|
variables: {
|
||||||
|
billFilters: {
|
||||||
|
batch: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
const [wizard, setWizard] = useState(false)
|
const [wizard, setWizard] = useState(false)
|
||||||
const [machineId, setMachineId] = useState('')
|
const [machineId, setMachineId] = useState('')
|
||||||
|
|
||||||
|
|
@ -204,10 +207,14 @@ const CashCassettes = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cashbox',
|
name: 'cashbox',
|
||||||
header: 'Cash-in',
|
header: 'Cash box',
|
||||||
width: maxNumberOfCassettes > 2 ? 140 : 280,
|
width: maxNumberOfCassettes > 2 ? 140 : 280,
|
||||||
view: value => (
|
view: (value, { id }) => (
|
||||||
<CashIn currency={{ code: fiatCurrency }} notes={value} total={0} />
|
<CashIn
|
||||||
|
currency={{ code: fiatCurrency }}
|
||||||
|
notes={value}
|
||||||
|
total={R.sum(R.map(it => it.fiat, bills[id] ?? []))}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
input: NumberInput,
|
input: NumberInput,
|
||||||
inputProps: {
|
inputProps: {
|
||||||
|
|
@ -222,7 +229,7 @@ const CashCassettes = () => {
|
||||||
elements.push({
|
elements.push({
|
||||||
name: `cassette${it}`,
|
name: `cassette${it}`,
|
||||||
header: `Cassette ${it}`,
|
header: `Cassette ${it}`,
|
||||||
width: (maxNumberOfCassettes > 2 ? 700 : 560) / maxNumberOfCassettes,
|
width: (maxNumberOfCassettes > 2 ? 560 : 650) / maxNumberOfCassettes,
|
||||||
stripe: true,
|
stripe: true,
|
||||||
doubleHeader: 'Cash-out',
|
doubleHeader: 'Cash-out',
|
||||||
view: (value, { id }) => (
|
view: (value, { id }) => (
|
||||||
|
|
@ -268,11 +275,12 @@ const CashCassettes = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
!dataLoading && (
|
||||||
<>
|
<>
|
||||||
<TitleSection
|
<TitleSection
|
||||||
title="Cash Cassettes"
|
title="Cash Boxes & Cassettes"
|
||||||
button={{
|
button={{
|
||||||
text: 'Cashbox history',
|
text: 'Cash box history',
|
||||||
icon: HistoryIcon,
|
icon: HistoryIcon,
|
||||||
inverseIcon: ReverseHistoryIcon,
|
inverseIcon: ReverseHistoryIcon,
|
||||||
toggle: setShowHistory
|
toggle: setShowHistory
|
||||||
|
|
@ -281,11 +289,11 @@ const CashCassettes = () => {
|
||||||
className={classes.tableWidth}>
|
className={classes.tableWidth}>
|
||||||
{!showHistory && (
|
{!showHistory && (
|
||||||
<Box alignItems="center" justifyContent="flex-end">
|
<Box alignItems="center" justifyContent="flex-end">
|
||||||
<Label1 className={classes.cashboxReset}>Cashbox reset</Label1>
|
<Label1 className={classes.cashboxReset}>Cash box resets</Label1>
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
justifyContent="flex-end"
|
justifyContent="end"
|
||||||
mr="-4px">
|
mr="-4px">
|
||||||
{cashboxReset && (
|
{cashboxReset && (
|
||||||
<P className={classes.selection}>
|
<P className={classes.selection}>
|
||||||
|
|
@ -327,12 +335,12 @@ const CashCassettes = () => {
|
||||||
currencyCode={fiatCurrency}
|
currencyCode={fiatCurrency}
|
||||||
machines={machines}
|
machines={machines}
|
||||||
config={config}
|
config={config}
|
||||||
bills={bills}
|
bills={R.path(['bills'])(data)}
|
||||||
deviceIds={deviceIds}
|
deviceIds={deviceIds}
|
||||||
/>
|
/>
|
||||||
{wizard && (
|
{wizard && (
|
||||||
<Wizard
|
<Wizard
|
||||||
machine={R.find(R.propEq('id', machineId))(machines)}
|
machine={R.find(R.propEq('id', machineId), machines)}
|
||||||
cashoutSettings={getCashoutSettings(machineId)}
|
cashoutSettings={getCashoutSettings(machineId)}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setWizard(false)
|
setWizard(false)
|
||||||
|
|
@ -344,13 +352,13 @@ const CashCassettes = () => {
|
||||||
)}
|
)}
|
||||||
{editingSchema && (
|
{editingSchema && (
|
||||||
<Modal
|
<Modal
|
||||||
title={'Cashbox reset'}
|
title={'Cash box resets'}
|
||||||
width={478}
|
width={478}
|
||||||
handleClose={() => setEditingSchema(null)}
|
handleClose={() => setEditingSchema(null)}
|
||||||
open={true}>
|
open={true}>
|
||||||
<P className={classes.descriptions}>
|
<P className={classes.descriptions}>
|
||||||
Specify if you want your cash-in counts to be reset automatically or
|
We can automatically assume you emptied a bill validator's cash
|
||||||
manually.
|
box when the machine detects that it has been removed.
|
||||||
</P>
|
</P>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
name="set-automatic-reset"
|
name="set-automatic-reset"
|
||||||
|
|
@ -360,8 +368,9 @@ const CashCassettes = () => {
|
||||||
className={classes.radioButtons}
|
className={classes.radioButtons}
|
||||||
/>
|
/>
|
||||||
<P className={classes.descriptions}>
|
<P className={classes.descriptions}>
|
||||||
Choose this option if you want your cash-in cashbox count to be
|
Assume the cash box is emptied whenever it's removed, creating a
|
||||||
reset automatically when it is physically removed from the machine.
|
new batch on the history screen and setting its current balance to
|
||||||
|
zero.
|
||||||
</P>
|
</P>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
name="set-manual-reset"
|
name="set-manual-reset"
|
||||||
|
|
@ -371,9 +380,9 @@ const CashCassettes = () => {
|
||||||
className={classes.radioButtons}
|
className={classes.radioButtons}
|
||||||
/>
|
/>
|
||||||
<P className={classes.descriptions}>
|
<P className={classes.descriptions}>
|
||||||
Choose this option if you want to edit your cash-in counts manually
|
Cash boxes won't be assumed emptied when removed, nor their counts
|
||||||
on Lamassu Admin, after you physically remove the bills from the
|
modified. Instead, to update the count and create a new batch,
|
||||||
cashbox.
|
you'll click the 'Edit' button on this panel.
|
||||||
</P>
|
</P>
|
||||||
<DialogActions className={classes.actions}>
|
<DialogActions className={classes.actions}>
|
||||||
<Button onClick={() => saveCashboxOption(selectedRadio)}>
|
<Button onClick={() => saveCashboxOption(selectedRadio)}>
|
||||||
|
|
@ -384,6 +393,7 @@ const CashCassettes = () => {
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CashCassettes
|
export default CashCassettes
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,17 @@
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
// import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import { Info1, Info2, Info3 } from 'src/components/typography/index'
|
import { Info1, Info2, Info3 } from 'src/components/typography/index'
|
||||||
// import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
import { fromNamespace } from 'src/utils/config'
|
import { fromNamespace } from 'src/utils/config'
|
||||||
|
import { numberToFiatAmount } from 'src/utils/number.js'
|
||||||
|
|
||||||
import styles from './CashCassettesFooter.styles.js'
|
import styles from './CashCassettesFooter.styles.js'
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
/* const sortDate = function(a, b) {
|
|
||||||
return new Date(b.created).getTime() - new Date(a.created).getTime()
|
|
||||||
} */
|
|
||||||
|
|
||||||
const CashCassettesFooter = ({
|
const CashCassettesFooter = ({
|
||||||
machines,
|
machines,
|
||||||
config,
|
config,
|
||||||
|
|
@ -43,44 +40,34 @@ const CashCassettesFooter = ({
|
||||||
|
|
||||||
const totalInCassettes = R.sum(R.reduce(reducerFn, [0, 0, 0, 0], machines))
|
const totalInCassettes = R.sum(R.reduce(reducerFn, [0, 0, 0, 0], machines))
|
||||||
|
|
||||||
/* const totalInCashBox = R.sum(
|
const totalInCashBox = R.sum(R.map(it => it.fiat)(bills))
|
||||||
R.flatten(
|
|
||||||
R.map(id => {
|
|
||||||
const sliceIdx = R.path([id, 0, 'cashbox'])(bills) ?? 0
|
|
||||||
return R.map(
|
|
||||||
R.prop('fiat'),
|
|
||||||
R.slice(0, sliceIdx, R.sort(sortDate, bills[id] ?? []))
|
|
||||||
)
|
|
||||||
}, deviceIds)
|
|
||||||
)
|
|
||||||
) */
|
|
||||||
|
|
||||||
// const total = new BigNumber(totalInCassettes + totalInCashBox).toFormat(0)
|
const total = new BigNumber(totalInCassettes + totalInCashBox).toFormat(0)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.footerContainer}>
|
<div className={classes.footerContainer}>
|
||||||
<div className={classes.footerContent}>
|
<div className={classes.footerContent}>
|
||||||
<Info3 className={classes.footerLabel}>Cash value in System</Info3>
|
<Info3 className={classes.footerLabel}>Cash value in System</Info3>
|
||||||
{/* <div className={classes.flex}>
|
<div className={classes.flex}>
|
||||||
<TxInIcon className={classes.icon} />
|
<TxInIcon className={classes.icon} />
|
||||||
<Info2 className={classes.iconLabel}>Cash-in:</Info2>
|
<Info2 className={classes.iconLabel}>Cash-in:</Info2>
|
||||||
<Info1 className={classes.valueDisplay}>
|
<Info1 className={classes.valueDisplay}>
|
||||||
{totalInCashBox} {currencyCode}
|
{numberToFiatAmount(totalInCashBox)} {currencyCode}
|
||||||
</Info1>
|
</Info1>
|
||||||
</div> */}
|
</div>
|
||||||
<div className={classes.flex}>
|
<div className={classes.flex}>
|
||||||
<TxOutIcon className={classes.icon} />
|
<TxOutIcon className={classes.icon} />
|
||||||
<Info2 className={classes.iconLabel}>Cash-out:</Info2>
|
<Info2 className={classes.iconLabel}>Cash-out:</Info2>
|
||||||
<Info1 className={classes.valueDisplay}>
|
<Info1 className={classes.valueDisplay}>
|
||||||
{totalInCassettes} {currencyCode}
|
{numberToFiatAmount(totalInCassettes)} {currencyCode}
|
||||||
</Info1>
|
</Info1>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className={classes.flex}>
|
<div className={classes.flex}>
|
||||||
<Info2 className={classes.iconLabel}>Total:</Info2>
|
<Info2 className={classes.iconLabel}>Total:</Info2>
|
||||||
<Info1 className={classes.valueDisplay}>
|
<Info1 className={classes.valueDisplay}>
|
||||||
{total} {currencyCode}
|
{numberToFiatAmount(total)} {currencyCode}
|
||||||
</Info1>
|
</Info1>
|
||||||
</div> */}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,7 @@ export default {
|
||||||
boxShadow: [[0, -1, 10, 0, 'rgba(50, 50, 50, 0.1)']]
|
boxShadow: [[0, -1, 10, 0, 'rgba(50, 50, 50, 0.1)']]
|
||||||
},
|
},
|
||||||
flex: {
|
flex: {
|
||||||
display: 'flex',
|
display: 'flex'
|
||||||
// temp marginLeft until cashIn square is enabled
|
|
||||||
marginLeft: -640
|
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { useQuery, useMutation } from '@apollo/react-hooks'
|
import { useQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React from 'react'
|
||||||
import * as Yup from 'yup'
|
// import * as Yup from 'yup'
|
||||||
|
|
||||||
import { Link, IconButton } from 'src/components/buttons'
|
// import { Link, IconButton } from 'src/components/buttons'
|
||||||
import { TextInput } from 'src/components/inputs'
|
// import { TextInput } from 'src/components/inputs'
|
||||||
import { NumberInput } from 'src/components/inputs/formik'
|
import { NumberInput } from 'src/components/inputs/formik'
|
||||||
import DataTable from 'src/components/tables/DataTable'
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
// import { ReactComponent as EditIconDisabled } from 'src/styling/icons/action/edit/disabled.svg'
|
||||||
|
// import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
||||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
import { formatDate } from 'src/utils/timezones'
|
import { formatDate } from 'src/utils/timezones'
|
||||||
|
|
@ -33,13 +34,13 @@ const GET_BATCHES = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const EDIT_BATCH = gql`
|
/* const EDIT_BATCH = gql`
|
||||||
mutation editBatch($id: ID, $performedBy: String) {
|
mutation editBatch($id: ID, $performedBy: String) {
|
||||||
editBatch(id: $id, performedBy: $performedBy) {
|
editBatch(id: $id, performedBy: $performedBy) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
` */
|
||||||
|
|
||||||
const GET_DATA = gql`
|
const GET_DATA = gql`
|
||||||
query getData {
|
query getData {
|
||||||
|
|
@ -63,27 +64,29 @@ const styles = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = Yup.object().shape({
|
/* const schema = Yup.object().shape({
|
||||||
performedBy: Yup.string().nullable()
|
performedBy: Yup.string().nullable()
|
||||||
})
|
}) */
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const CashboxHistory = ({ machines, currency }) => {
|
const CashboxHistory = ({ machines, currency }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [error, setError] = useState(false)
|
|
||||||
const [fields, setFields] = useState([])
|
/* const [error, setError] = useState(false)
|
||||||
|
const [field, setField] = useState(null)
|
||||||
|
const [editing, setEditing] = useState(false) */
|
||||||
|
|
||||||
const { data: batchesData, loading: batchesLoading } = useQuery(GET_BATCHES)
|
const { data: batchesData, loading: batchesLoading } = useQuery(GET_BATCHES)
|
||||||
|
|
||||||
const [editBatch] = useMutation(EDIT_BATCH, {
|
/* const [editBatch] = useMutation(EDIT_BATCH, {
|
||||||
refetchQueries: () => ['cashboxBatches']
|
refetchQueries: () => ['cashboxBatches']
|
||||||
})
|
}) */
|
||||||
|
|
||||||
const { data: configData, loading: configLoading } = useQuery(GET_DATA)
|
const { data: configData, loading: configLoading } = useQuery(GET_DATA)
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configData)
|
const timezone = R.path(['config', 'locale_timezone'], configData)
|
||||||
|
|
||||||
const loading = batchesLoading && configLoading
|
const loading = batchesLoading || configLoading
|
||||||
|
|
||||||
const batches = R.path(['cashboxBatches'])(batchesData)
|
const batches = R.path(['cashboxBatches'])(batchesData)
|
||||||
|
|
||||||
|
|
@ -91,33 +94,36 @@ const CashboxHistory = ({ machines, currency }) => {
|
||||||
(ret, i) =>
|
(ret, i) =>
|
||||||
R.pipe(
|
R.pipe(
|
||||||
R.assoc(
|
R.assoc(
|
||||||
`cash-out-${i}-refill`,
|
`cash-cassette-${i}-refill`,
|
||||||
<>
|
<>
|
||||||
<TxOutIcon />
|
<TxOutIcon />
|
||||||
<span className={classes.operationType}>Cash-out {i} refill</span>
|
<span className={classes.operationType}>
|
||||||
|
Cash cassette {i} refill
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
R.assoc(
|
R.assoc(
|
||||||
`cash-out-${i}-empty`,
|
`cash-cassette-${i}-empty`,
|
||||||
<>
|
<>
|
||||||
<TxOutIcon />
|
<TxOutIcon />
|
||||||
<span className={classes.operationType}>Cash-out {i} emptied</span>
|
<span className={classes.operationType}>
|
||||||
|
Cash cassette {i} emptied
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
)(ret),
|
)(ret),
|
||||||
{
|
{
|
||||||
'cash-in-empty': (
|
'cash-box-empty': (
|
||||||
<>
|
<>
|
||||||
<TxInIcon />
|
<TxInIcon />
|
||||||
<span className={classes.operationType}>Cash-in emptied</span>
|
<span className={classes.operationType}>Cash box emptied</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
R.range(1, 5)
|
R.range(1, 5)
|
||||||
)
|
)
|
||||||
|
|
||||||
const save = row => {
|
/* const save = row => {
|
||||||
const field = R.find(f => f.id === row.id, fields)
|
|
||||||
const performedBy = field.performedBy === '' ? null : field.performedBy
|
const performedBy = field.performedBy === '' ? null : field.performedBy
|
||||||
|
|
||||||
schema
|
schema
|
||||||
|
|
@ -129,14 +135,15 @@ const CashboxHistory = ({ machines, currency }) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(setError(true))
|
.catch(setError(true))
|
||||||
return close(row.id)
|
return close()
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = id => {
|
const close = () => {
|
||||||
setFields(R.filter(f => f.id !== id, fields))
|
setEditing(false)
|
||||||
|
setField(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const notEditing = id => !R.any(R.propEq('id', id), fields)
|
const notEditing = id => field?.id !== id */
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
|
|
@ -174,7 +181,7 @@ const CashboxHistory = ({ machines, currency }) => {
|
||||||
{
|
{
|
||||||
name: 'total',
|
name: 'total',
|
||||||
header: 'Total',
|
header: 'Total',
|
||||||
width: 100,
|
width: 180,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it => (
|
view: it => (
|
||||||
<span>
|
<span>
|
||||||
|
|
@ -195,8 +202,8 @@ const CashboxHistory = ({ machines, currency }) => {
|
||||||
width: 125,
|
width: 125,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it => formatDate(it.created, timezone, 'HH:mm')
|
view: it => formatDate(it.created, timezone, 'HH:mm')
|
||||||
},
|
}
|
||||||
{
|
/* {
|
||||||
name: 'performedBy',
|
name: 'performedBy',
|
||||||
header: 'Performed by',
|
header: 'Performed by',
|
||||||
width: 180,
|
width: 180,
|
||||||
|
|
@ -206,21 +213,10 @@ const CashboxHistory = ({ machines, currency }) => {
|
||||||
return R.isNil(it.performedBy) ? 'Unknown entity' : it.performedBy
|
return R.isNil(it.performedBy) ? 'Unknown entity' : it.performedBy
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
onChange={e =>
|
onChange={e => setField({ ...field, performedBy: e.target.value })}
|
||||||
setFields(
|
|
||||||
R.map(
|
|
||||||
f =>
|
|
||||||
f.id === it.id ? { ...f, performedBy: e.target.value } : f,
|
|
||||||
fields
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
error={error}
|
error={error}
|
||||||
width={190 * 0.85}
|
width={190 * 0.85}
|
||||||
value={R.prop(
|
value={field?.performedBy}
|
||||||
'performedBy',
|
|
||||||
R.find(f => f.id === it.id, fields)
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -228,19 +224,18 @@ const CashboxHistory = ({ machines, currency }) => {
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
header: 'Edit',
|
header: 'Edit',
|
||||||
width: 150,
|
width: 80,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
view: it => {
|
view: it => {
|
||||||
if (notEditing(it.id))
|
if (notEditing(it.id))
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
|
disabled={editing}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFields([
|
setField({ id: it.id, performedBy: it.performedBy })
|
||||||
...fields,
|
setEditing(true)
|
||||||
{ id: it.id, performedBy: it.performedBy }
|
|
||||||
])
|
|
||||||
}}>
|
}}>
|
||||||
<EditIcon />
|
{editing ? <EditIconDisabled /> : <EditIcon />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
|
|
@ -248,26 +243,23 @@ const CashboxHistory = ({ machines, currency }) => {
|
||||||
<Link type="submit" color="primary" onClick={() => save(it)}>
|
<Link type="submit" color="primary" onClick={() => save(it)}>
|
||||||
Save
|
Save
|
||||||
</Link>
|
</Link>
|
||||||
<Link color="secondary" onClick={() => close(it.id)}>
|
<Link color="secondary" onClick={close}>
|
||||||
Cancel
|
Cancel
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
{!loading && (
|
|
||||||
<DataTable
|
<DataTable
|
||||||
|
loading={loading}
|
||||||
name="cashboxHistory"
|
name="cashboxHistory"
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={batches}
|
data={batches}
|
||||||
emptyText="No cashbox batches so far"
|
emptyText="No cashbox batches so far"
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,11 @@ const MachineStatus = () => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
const { state } = useLocation()
|
const { state } = useLocation()
|
||||||
const addedMachineId = state?.id
|
const addedMachineId = state?.id
|
||||||
const { data: machinesResponse, refetch, loading } = useQuery(GET_MACHINES)
|
const {
|
||||||
|
data: machinesResponse,
|
||||||
|
refetch,
|
||||||
|
loading: machinesLoading
|
||||||
|
} = useQuery(GET_MACHINES)
|
||||||
const { data: configResponse, configLoading } = useQuery(GET_DATA)
|
const { data: configResponse, configLoading } = useQuery(GET_DATA)
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||||
|
|
||||||
|
|
@ -114,6 +118,8 @@ const MachineStatus = () => {
|
||||||
<MachineDetailsRow it={it} onActionSuccess={refetch} timezone={timezone} />
|
<MachineDetailsRow it={it} onActionSuccess={refetch} timezone={timezone} />
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const loading = machinesLoading || configLoading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.titleWrapper}>
|
<div className={classes.titleWrapper}>
|
||||||
|
|
@ -132,7 +138,7 @@ const MachineStatus = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DataTable
|
<DataTable
|
||||||
loading={loading && configLoading}
|
loading={loading}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={machines}
|
data={machines}
|
||||||
Details={InnerMachineDetailsRow}
|
Details={InnerMachineDetailsRow}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => {
|
||||||
|
|
||||||
const onContinue = it => {
|
const onContinue = it => {
|
||||||
const newConfig = R.merge(config, it)
|
const newConfig = R.merge(config, it)
|
||||||
|
|
||||||
if (isLastStep) {
|
if (isLastStep) {
|
||||||
const wasCashboxEmptied = [
|
const wasCashboxEmptied = [
|
||||||
config?.wasCashboxEmptied,
|
config?.wasCashboxEmptied,
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ const WizardSplash = ({ name, onContinue }) => {
|
||||||
<div className={classes.warningInfo}>
|
<div className={classes.warningInfo}>
|
||||||
<WarningIcon className={classes.warningIcon} />
|
<WarningIcon className={classes.warningIcon} />
|
||||||
<P noMargin className={classes.warningText}>
|
<P noMargin className={classes.warningText}>
|
||||||
For cash-out cassettes, please make sure you've removed the remaining
|
For cash cassettes, please make sure you've removed the remaining
|
||||||
bills before adding the new ones.
|
bills before adding the new ones.
|
||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import tejo4CassetteThree from 'src/styling/icons/cassettes/tejo/4-cassettes/4-c
|
||||||
import tejo4CassetteFour from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-4-left.svg'
|
import tejo4CassetteFour from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-4-left.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
import { comet, errorColor } from 'src/styling/variables'
|
import { comet, errorColor } from 'src/styling/variables'
|
||||||
|
import { numberToFiatAmount } from 'src/utils/number'
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
content: {
|
content: {
|
||||||
|
|
@ -115,7 +116,8 @@ const WizardStep = ({
|
||||||
lastStep,
|
lastStep,
|
||||||
steps,
|
steps,
|
||||||
fiatCurrency,
|
fiatCurrency,
|
||||||
onContinue
|
onContinue,
|
||||||
|
initialValues
|
||||||
}) => {
|
}) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
|
@ -168,7 +170,7 @@ const WizardStep = ({
|
||||||
classes.verticalAlign,
|
classes.verticalAlign,
|
||||||
classes.fullWidth
|
classes.fullWidth
|
||||||
)}>
|
)}>
|
||||||
<H4 noMargin>Did you empty the cash-in box?</H4>
|
<H4 noMargin>Did you empty the cash box?</H4>
|
||||||
<Field
|
<Field
|
||||||
component={RadioGroup}
|
component={RadioGroup}
|
||||||
name="wasCashboxEmptied"
|
name="wasCashboxEmptied"
|
||||||
|
|
@ -188,8 +190,8 @@ const WizardStep = ({
|
||||||
<P>Since previous update</P>
|
<P>Since previous update</P>
|
||||||
<Tooltip width={215}>
|
<Tooltip width={215}>
|
||||||
<P>
|
<P>
|
||||||
Number of bills inside the cashbox, since the last
|
Number of bills inside the cash box, since the last
|
||||||
cashbox changes.
|
cash box changes.
|
||||||
</P>
|
</P>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -219,12 +221,7 @@ const WizardStep = ({
|
||||||
validateOnBlur={false}
|
validateOnBlur={false}
|
||||||
validateOnChange={false}
|
validateOnChange={false}
|
||||||
onSubmit={onContinue}
|
onSubmit={onContinue}
|
||||||
initialValues={{
|
initialValues={initialValues}
|
||||||
cassette1: '',
|
|
||||||
cassette2: '',
|
|
||||||
cassette3: '',
|
|
||||||
cassette4: ''
|
|
||||||
}}
|
|
||||||
enableReinitialize
|
enableReinitialize
|
||||||
validationSchema={steps[step - 1].schema}>
|
validationSchema={steps[step - 1].schema}>
|
||||||
{({ values, errors }) => (
|
{({ values, errors }) => (
|
||||||
|
|
@ -255,7 +252,7 @@ const WizardStep = ({
|
||||||
<H4
|
<H4
|
||||||
className={classes.cassetteFormTitleContent}
|
className={classes.cassetteFormTitleContent}
|
||||||
noMargin>
|
noMargin>
|
||||||
Cash-out {step - 1} (dispenser)
|
Cash cassette {step - 1} (dispenser)
|
||||||
</H4>
|
</H4>
|
||||||
</div>
|
</div>
|
||||||
<Cashbox
|
<Cashbox
|
||||||
|
|
@ -283,7 +280,8 @@ const WizardStep = ({
|
||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
<P noMargin className={classes.fiatTotal}>
|
<P noMargin className={classes.fiatTotal}>
|
||||||
= {cassetteTotal(values)} {fiatCurrency}
|
= {numberToFiatAmount(cassetteTotal(values))}{' '}
|
||||||
|
{fiatCurrency}
|
||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,10 @@ const FiatBalanceOverrides = ({ section }) => {
|
||||||
it => {
|
it => {
|
||||||
elements.push({
|
elements.push({
|
||||||
name: `fillingPercentageCassette${it}`,
|
name: `fillingPercentageCassette${it}`,
|
||||||
display: `Cash-out ${it}`,
|
display: `Cash cassette ${it}`,
|
||||||
width: 155,
|
width: 155,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
doubleHeader: 'Cash-out (Cassette Empty)',
|
doubleHeader: 'Cash Cassette Empty',
|
||||||
bold: true,
|
bold: true,
|
||||||
input: NumberInput,
|
input: NumberInput,
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
|
|
|
||||||
|
|
@ -98,13 +98,13 @@ const Logs = () => {
|
||||||
const [saveMessage, setSaveMessage] = useState(null)
|
const [saveMessage, setSaveMessage] = useState(null)
|
||||||
const [logLevel, setLogLevel] = useState(SHOW_ALL)
|
const [logLevel, setLogLevel] = useState(SHOW_ALL)
|
||||||
|
|
||||||
const { data, loading } = useQuery(GET_SERVER_DATA, {
|
const { data, loading: dataLoading } = useQuery(GET_SERVER_DATA, {
|
||||||
onCompleted: () => setSaveMessage(''),
|
onCompleted: () => setSaveMessage(''),
|
||||||
variables: {
|
variables: {
|
||||||
limit: NUM_LOG_RESULTS
|
limit: NUM_LOG_RESULTS
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const { data: configResponse, configLoading } = useQuery(GET_DATA)
|
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||||
|
|
||||||
const defaultLogLevels = [
|
const defaultLogLevels = [
|
||||||
|
|
@ -132,6 +132,8 @@ const Logs = () => {
|
||||||
setLogLevel(logLevel)
|
setLogLevel(logLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loading = dataLoading || configLoading
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.titleWrapper}>
|
<div className={classes.titleWrapper}>
|
||||||
|
|
@ -206,8 +208,8 @@ const Logs = () => {
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
{loading && configLoading && <H4>{'Loading...'}</H4>}
|
{loading && <H4>{'Loading...'}</H4>}
|
||||||
{!loading && !configLoading && !data?.serverLogs?.length && (
|
{!loading && !data?.serverLogs?.length && (
|
||||||
<H4>{'No activity so far'}</H4>
|
<H4>{'No activity so far'}</H4>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export default {
|
||||||
settings: {
|
settings: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
disabledMessage: 'RBF verification not available',
|
disabledMessage: 'RBF verification not available',
|
||||||
label: 'Enable RBF verification',
|
label: 'Lower the confidence of RBF transactions',
|
||||||
requirement: 'bitcoind'
|
requirement: 'bitcoind'
|
||||||
},
|
},
|
||||||
face: true
|
face: true
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ const SessionManagement = () => {
|
||||||
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
|
||||||
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
const timezone = R.path(['config', 'locale_timezone'], configResponse)
|
||||||
|
|
||||||
const loading = sessionsLoading && configLoading
|
const loading = sessionsLoading || configLoading
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
{
|
{
|
||||||
|
|
@ -61,7 +61,7 @@ const SessionManagement = () => {
|
||||||
{
|
{
|
||||||
header: 'Last known use',
|
header: 'Last known use',
|
||||||
width: 305,
|
width: 305,
|
||||||
textAlign: 'center',
|
textAlign: 'left',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
view: s => {
|
view: s => {
|
||||||
if (R.isNil(s.sess.ua)) return 'No Record'
|
if (R.isNil(s.sess.ua)) return 'No Record'
|
||||||
|
|
@ -72,7 +72,7 @@ const SessionManagement = () => {
|
||||||
{
|
{
|
||||||
header: 'Last known location',
|
header: 'Last known location',
|
||||||
width: 250,
|
width: 250,
|
||||||
textAlign: 'center',
|
textAlign: 'left',
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
view: s => {
|
view: s => {
|
||||||
return isLocalhost(s.sess.ipAddress) ? 'This device' : s.sess.ipAddress
|
return isLocalhost(s.sess.ipAddress) ? 'This device' : s.sess.ipAddress
|
||||||
|
|
@ -107,16 +107,15 @@ const SessionManagement = () => {
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!loading && (
|
|
||||||
<>
|
<>
|
||||||
<TitleSection title="Session Management" />
|
<TitleSection title="Session Management" />
|
||||||
<DataTable
|
<DataTable
|
||||||
|
loading={loading}
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={R.path(['sessions'])(tknResponse)}
|
data={R.path(['sessions'])(tknResponse)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SessionManagement
|
export default SessionManagement
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import DataTable from 'src/components/tables/DataTable'
|
||||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||||
import { ReactComponent as CustomerLinkIcon } from 'src/styling/icons/month arrows/right.svg'
|
import { ReactComponent as CustomerLinkIcon } from 'src/styling/icons/month arrows/right.svg'
|
||||||
|
import { ReactComponent as CustomerLinkWhiteIcon } from 'src/styling/icons/month arrows/right_white.svg'
|
||||||
|
import { errorColor } from 'src/styling/variables'
|
||||||
import { formatDate } from 'src/utils/timezones'
|
import { formatDate } from 'src/utils/timezones'
|
||||||
|
|
||||||
import DetailsRow from './DetailsCard'
|
import DetailsRow from './DetailsCard'
|
||||||
|
|
@ -124,13 +126,13 @@ const Transactions = () => {
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
|
|
||||||
const [filters, setFilters] = useState([])
|
const [filters, setFilters] = useState([])
|
||||||
const { data: filtersResponse, loading: loadingFilters } = useQuery(
|
const { data: filtersResponse, loading: filtersLoading } = useQuery(
|
||||||
GET_TRANSACTION_FILTERS
|
GET_TRANSACTION_FILTERS
|
||||||
)
|
)
|
||||||
const [variables, setVariables] = useState({ limit: NUM_LOG_RESULTS })
|
const [variables, setVariables] = useState({ limit: NUM_LOG_RESULTS })
|
||||||
const {
|
const {
|
||||||
data: txData,
|
data: txData,
|
||||||
loading: loadingTransactions,
|
loading: transactionsLoading,
|
||||||
refetch,
|
refetch,
|
||||||
startPolling,
|
startPolling,
|
||||||
stopPolling
|
stopPolling
|
||||||
|
|
@ -185,7 +187,11 @@ const Transactions = () => {
|
||||||
<div className={classes.overflowTd}>{getCustomerDisplayName(it)}</div>
|
<div className={classes.overflowTd}>{getCustomerDisplayName(it)}</div>
|
||||||
{!it.isAnonymous && (
|
{!it.isAnonymous && (
|
||||||
<div onClick={() => redirect(it.customerId)}>
|
<div onClick={() => redirect(it.customerId)}>
|
||||||
|
{it.hasError ? (
|
||||||
|
<CustomerLinkWhiteIcon className={classes.customerLinkIcon} />
|
||||||
|
) : (
|
||||||
<CustomerLinkIcon className={classes.customerLinkIcon} />
|
<CustomerLinkIcon className={classes.customerLinkIcon} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -294,6 +300,14 @@ const Transactions = () => {
|
||||||
|
|
||||||
const filterOptions = R.path(['transactionFilters'])(filtersResponse)
|
const filterOptions = R.path(['transactionFilters'])(filtersResponse)
|
||||||
|
|
||||||
|
const loading = transactionsLoading || filtersLoading || configLoading
|
||||||
|
|
||||||
|
const errorLabel = (
|
||||||
|
<svg width={12} height={12}>
|
||||||
|
<rect width={12} height={12} rx={3} fill={errorColor} />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.titleWrapper}>
|
<div className={classes.titleWrapper}>
|
||||||
|
|
@ -301,7 +315,7 @@ const Transactions = () => {
|
||||||
<Title>Transactions</Title>
|
<Title>Transactions</Title>
|
||||||
<div className={classes.buttonsWrapper}>
|
<div className={classes.buttonsWrapper}>
|
||||||
<SearchBox
|
<SearchBox
|
||||||
loading={loadingFilters}
|
loading={filtersLoading}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
options={filterOptions}
|
options={filterOptions}
|
||||||
inputPlaceholder={'Search Transactions'}
|
inputPlaceholder={'Search Transactions'}
|
||||||
|
|
@ -331,6 +345,10 @@ const Transactions = () => {
|
||||||
<TxOutIcon />
|
<TxOutIcon />
|
||||||
<span>Cash-out</span>
|
<span>Cash-out</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
{errorLabel}
|
||||||
|
<span>Transaction error</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{filters.length > 0 && (
|
{filters.length > 0 && (
|
||||||
|
|
@ -342,7 +360,7 @@ const Transactions = () => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<DataTable
|
<DataTable
|
||||||
loading={loadingTransactions && configLoading}
|
loading={loading}
|
||||||
emptyText="No transactions so far"
|
emptyText="No transactions so far"
|
||||||
elements={elements}
|
elements={elements}
|
||||||
data={txList}
|
data={txList}
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,11 @@ const mainStyles = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
|
'& > div': {
|
||||||
|
marginLeft: 24
|
||||||
|
},
|
||||||
'& > div:first-child': {
|
'& > div:first-child': {
|
||||||
marginRight: 24
|
marginLeft: 0
|
||||||
},
|
},
|
||||||
'& span': {
|
'& span': {
|
||||||
extend: label1,
|
extend: label1,
|
||||||
|
|
|
||||||
|
|
@ -183,10 +183,10 @@ const InfoPanel = ({ step, config = {}, liveValues = {}, currency }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<H5 className={classes.infoTitle}>Trigger overview so far</H5>
|
<H5 className={classes.infoTitle}>Trigger overview so far</H5>
|
||||||
<Info3 noMargin className={classes.infoText}>
|
<Info3 noMargin>
|
||||||
{oldText}
|
{oldText}
|
||||||
{step !== 1 && ', '}
|
{step !== 1 && ', '}
|
||||||
{newText}
|
<span className={classes.infoCurrentText}>{newText}</span>
|
||||||
{!isLastStep && '...'}
|
{!isLastStep && '...'}
|
||||||
</Info3>
|
</Info3>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ const getDefaultSettings = () => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'expirationTime',
|
name: 'expirationTime',
|
||||||
header: 'Expiration Time',
|
header: 'Expiration time',
|
||||||
width: 196,
|
width: 196,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
editable: false
|
editable: false
|
||||||
|
|
@ -101,7 +101,7 @@ const getOverrides = () => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'expirationTime',
|
name: 'expirationTime',
|
||||||
header: 'Expiration Time',
|
header: 'Expiration time',
|
||||||
width: 196,
|
width: 196,
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
editable: false
|
editable: false
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,16 @@ import * as R from 'ramda'
|
||||||
import React, { useReducer, useState, useContext } from 'react'
|
import React, { useReducer, useState, useContext } from 'react'
|
||||||
|
|
||||||
import AppContext from 'src/AppContext'
|
import AppContext from 'src/AppContext'
|
||||||
import { Link } from 'src/components/buttons'
|
import { ActionButton, Link } from 'src/components/buttons'
|
||||||
import { Switch } from 'src/components/inputs'
|
import { Switch } from 'src/components/inputs'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import DataTable from 'src/components/tables/DataTable'
|
import DataTable from 'src/components/tables/DataTable'
|
||||||
|
import { ReactComponent as WhiteKeyIcon } from 'src/styling/icons/button/key/white.svg'
|
||||||
|
import { ReactComponent as KeyIcon } from 'src/styling/icons/button/key/zodiac.svg'
|
||||||
|
import { ReactComponent as WhiteLockIcon } from 'src/styling/icons/button/lock/white.svg'
|
||||||
|
import { ReactComponent as LockIcon } from 'src/styling/icons/button/lock/zodiac.svg'
|
||||||
|
import { ReactComponent as WhiteUserRoleIcon } from 'src/styling/icons/button/user-role/white.svg'
|
||||||
|
import { ReactComponent as UserRoleIcon } from 'src/styling/icons/button/user-role/zodiac.svg'
|
||||||
|
|
||||||
import styles from './UserManagement.styles'
|
import styles from './UserManagement.styles'
|
||||||
import ChangeRoleModal from './modals/ChangeRoleModal'
|
import ChangeRoleModal from './modals/ChangeRoleModal'
|
||||||
|
|
@ -153,35 +159,37 @@ const Users = () => {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
view: u => {
|
view: u => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={classes.actionButtonWrapper}>
|
||||||
<Chip
|
<ActionButton
|
||||||
size="small"
|
Icon={KeyIcon}
|
||||||
label="Reset password"
|
InverseIcon={WhiteKeyIcon}
|
||||||
className={classes.actionChip}
|
color="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserInfo(u)
|
setUserInfo(u)
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'open',
|
type: 'open',
|
||||||
payload: 'showResetPasswordModal'
|
payload: 'showResetPasswordModal'
|
||||||
})
|
})
|
||||||
}}
|
}}>
|
||||||
/>
|
Reset password
|
||||||
<Chip
|
</ActionButton>
|
||||||
size="small"
|
<ActionButton
|
||||||
label="Reset 2FA"
|
Icon={LockIcon}
|
||||||
className={classes.actionChip}
|
InverseIcon={WhiteLockIcon}
|
||||||
|
color="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserInfo(u)
|
setUserInfo(u)
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'open',
|
type: 'open',
|
||||||
payload: 'showReset2FAModal'
|
payload: 'showReset2FAModal'
|
||||||
})
|
})
|
||||||
}}
|
}}>
|
||||||
/>
|
Reset 2FA
|
||||||
<Chip
|
</ActionButton>
|
||||||
size="small"
|
<ActionButton
|
||||||
label="Add FIDO"
|
Icon={UserRoleIcon}
|
||||||
className={classes.actionChip}
|
InverseIcon={WhiteUserRoleIcon}
|
||||||
|
color="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserInfo(u)
|
setUserInfo(u)
|
||||||
generateAttestationOptions({
|
generateAttestationOptions({
|
||||||
|
|
@ -189,9 +197,10 @@ const Users = () => {
|
||||||
userID: u.id
|
userID: u.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}>
|
||||||
/>
|
Add FIDO
|
||||||
</>
|
</ActionButton>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,6 @@ const styles = {
|
||||||
fontFamily: fontPrimary,
|
fontFamily: fontPrimary,
|
||||||
marginLeft: 10
|
marginLeft: 10
|
||||||
},
|
},
|
||||||
actionChip: {
|
|
||||||
backgroundColor: subheaderColor,
|
|
||||||
marginRight: 15
|
|
||||||
},
|
|
||||||
info: {
|
info: {
|
||||||
fontFamily: fontSecondary,
|
fontFamily: fontSecondary,
|
||||||
textAlign: 'justify'
|
textAlign: 'justify'
|
||||||
|
|
@ -118,6 +114,10 @@ const styles = {
|
||||||
},
|
},
|
||||||
roleSwitch: {
|
roleSwitch: {
|
||||||
marginLeft: 15
|
marginLeft: 15
|
||||||
|
},
|
||||||
|
actionButtonWrapper: {
|
||||||
|
display: 'flex',
|
||||||
|
gap: 12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useLazyQuery } from '@apollo/react-hooks'
|
import { useLazyQuery } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import { Form, Formik } from 'formik'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
|
@ -48,6 +49,14 @@ const Input2FAModal = ({ showModal, handleClose, setConfirmation }) => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (twoFACode.length !== 6) {
|
||||||
|
setInvalidCode(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
confirm2FA({ variables: { code: twoFACode } })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
showModal && (
|
showModal && (
|
||||||
<Modal
|
<Modal
|
||||||
|
|
@ -61,6 +70,9 @@ const Input2FAModal = ({ showModal, handleClose, setConfirmation }) => {
|
||||||
To make changes on this user, please confirm this action by entering
|
To make changes on this user, please confirm this action by entering
|
||||||
your two-factor authentication code below.
|
your two-factor authentication code below.
|
||||||
</P>
|
</P>
|
||||||
|
{/* TODO: refactor the 2FA CodeInput to properly use Formik */}
|
||||||
|
<Formik onSubmit={() => {}} initialValues={{}}>
|
||||||
|
<Form>
|
||||||
<CodeInput
|
<CodeInput
|
||||||
name="2fa"
|
name="2fa"
|
||||||
value={twoFACode}
|
value={twoFACode}
|
||||||
|
|
@ -70,19 +82,14 @@ const Input2FAModal = ({ showModal, handleClose, setConfirmation }) => {
|
||||||
containerStyle={classes.codeContainer}
|
containerStyle={classes.codeContainer}
|
||||||
shouldAutoFocus
|
shouldAutoFocus
|
||||||
/>
|
/>
|
||||||
|
<button onClick={handleSubmit} className={classes.enterButton} />
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
{getErrorMsg() && (
|
{getErrorMsg() && (
|
||||||
<P className={classes.errorMessage}>{getErrorMsg()}</P>
|
<P className={classes.errorMessage}>{getErrorMsg()}</P>
|
||||||
)}
|
)}
|
||||||
<div className={classes.footer}>
|
<div className={classes.footer}>
|
||||||
<Button
|
<Button className={classes.submit} onClick={handleSubmit}>
|
||||||
className={classes.submit}
|
|
||||||
onClick={() => {
|
|
||||||
if (twoFACode.length !== 6) {
|
|
||||||
setInvalidCode(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
confirm2FA({ variables: { code: twoFACode } })
|
|
||||||
}}>
|
|
||||||
Confirm
|
Confirm
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,8 @@ const SAVE_ACCOUNTS = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const isConfigurable = it => !R.isNil(it) && !R.contains(it)(['mock-exchange'])
|
const isConfigurable = it =>
|
||||||
|
!R.isNil(it) && !R.contains(it)(['mock-exchange', 'no-exchange'])
|
||||||
|
|
||||||
const ChooseExchange = ({ data: currentData, addData }) => {
|
const ChooseExchange = ({ data: currentData, addData }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
|
||||||
11
new-lamassu-admin/src/styling/icons/button/key/white.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/key/white</title>
|
||||||
|
<g id="icon/button/key/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Group" transform="translate(0.500000, 0.500000)" stroke="#FFFFFF">
|
||||||
|
<circle id="Oval" cx="2.75" cy="8.25" r="2.75"></circle>
|
||||||
|
<line x1="5.04166667" y1="5.95833333" x2="11" y2="0" id="Path-13" stroke-linecap="round" stroke-linejoin="round"></line>
|
||||||
|
<line x1="8.25" y1="3.66666667" x2="10.5416667" y2="1.375" id="Path-13-Copy" stroke-width="2" stroke-linejoin="round"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 773 B |
11
new-lamassu-admin/src/styling/icons/button/key/zodiac.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/key/zodiac</title>
|
||||||
|
<g id="icon/button/key/zodiac" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Group" transform="translate(0.500000, 0.500000)" stroke="#1B2559">
|
||||||
|
<circle id="Oval" cx="2.75" cy="8.25" r="2.75"></circle>
|
||||||
|
<line x1="5.04166667" y1="5.95833333" x2="11" y2="0" id="Path-13" stroke-linecap="round" stroke-linejoin="round"></line>
|
||||||
|
<line x1="8.25" y1="3.66666667" x2="10.5416667" y2="1.375" id="Path-13-Copy" stroke-width="2" stroke-linejoin="round"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 775 B |
11
new-lamassu-admin/src/styling/icons/button/lock/white.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/lock/white</title>
|
||||||
|
<g id="icon/button/lock/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Lock-Icon-White" transform="translate(0.500000, 0.500000)">
|
||||||
|
<path d="M7.98058644,2.48058644 C7.98058644,1.11059638 6.86999006,0 5.5,0 C4.13000994,0 3.01941356,1.11059638 3.01941356,2.48058644 C3.01941356,3.39391315 3.01941356,4.09482878 3.01941356,4.58333333 L7.98058644,4.58333333 C7.98058644,4.09482878 7.98058644,3.39391315 7.98058644,2.48058644 Z" id="Lock" stroke="#FFFFFF" stroke-linejoin="round"></path>
|
||||||
|
<rect id="Body" stroke="#FFFFFF" stroke-linejoin="round" x="0" y="4.58333333" width="11" height="6.41666667"></rect>
|
||||||
|
<circle id="Key-Hole" fill="#FFFFFF" cx="5.5" cy="7.33333333" r="1"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1,010 B |
11
new-lamassu-admin/src/styling/icons/button/lock/zodiac.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/lock/zodiac</title>
|
||||||
|
<g id="icon/button/lock/zodiac" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="Lock-Icon-Zodiac" transform="translate(0.500000, 0.500000)">
|
||||||
|
<path d="M7.98058644,2.48058644 C7.98058644,1.11059638 6.86999006,0 5.5,0 C4.13000994,0 3.01941356,1.11059638 3.01941356,2.48058644 C3.01941356,3.39391315 3.01941356,4.09482878 3.01941356,4.58333333 L7.98058644,4.58333333 C7.98058644,4.09482878 7.98058644,3.39391315 7.98058644,2.48058644 Z" id="Lock" stroke="#1B2559" stroke-linejoin="round"></path>
|
||||||
|
<rect id="Body" stroke="#1B2559" stroke-linejoin="round" x="0" y="4.58333333" width="11" height="6.41666667"></rect>
|
||||||
|
<circle id="Key-Hole" fill="#1B2559" cx="5.5" cy="7.33333333" r="1"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1,013 B |
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/user-role/white</title>
|
||||||
|
<g id="icon/button/user-role/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
|
||||||
|
<g id="User-Role-Icon-White" transform="translate(2.500000, 0.500000)" stroke="#FFFFFF">
|
||||||
|
<path d="M5.50008791,6.84274776 L5.5,11 L3.66666667,9.35927189 L1.83333333,11 L1.83223109,6.84216075 C2.37179795,7.15453375 2.99835187,7.33333333 3.66666667,7.33333333 C4.33456272,7.33333333 4.96075021,7.15475774 5.50008791,6.84274776 Z" id="Bottom"></path>
|
||||||
|
<circle id="Top" cx="3.66666667" cy="3.66666667" r="3.66666667"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 840 B |
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="12px" height="12px" viewBox="0 0 12 12" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>icon/button/user-role/zodiac</title>
|
||||||
|
<g id="icon/button/user-role/zodiac" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
|
||||||
|
<g id="User-Role-Icon-Zodiac" transform="translate(2.500000, 0.500000)" stroke="#1B2559">
|
||||||
|
<path d="M5.50008791,6.84274776 L5.5,11 L3.66666667,9.35927189 L1.83333333,11 L1.83223109,6.84216075 C2.37179795,7.15453375 2.99835187,7.33333333 3.66666667,7.33333333 C4.33456272,7.33333333 4.96075021,7.15475774 5.50008791,6.84274776 Z" id="Bottom"></path>
|
||||||
|
<circle id="Top" cx="3.66666667" cy="3.66666667" r="3.66666667"></circle>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 843 B |
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?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">
|
||||||
|
<title>icon/customer-nav/photos/comet</title>
|
||||||
|
<g id="icon/customer-nav/photos/comet" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<rect id="Rectangle" stroke="#5F668A" stroke-width="2" x="1" y="1" width="18" height="18" rx="1"></rect>
|
||||||
|
<circle id="Oval" stroke="#5F668A" stroke-width="2" cx="15" cy="5" r="1"></circle>
|
||||||
|
<polyline id="Path" stroke="#5F668A" stroke-width="2" stroke-linejoin="round" points="1 19 7 13 13 19"></polyline>
|
||||||
|
<path d="M13.3333333,14 L18,19 L13.3333333,19 L11,16.5 L13.3333333,14 Z" id="Combined-Shape" stroke="#5F668A" stroke-width="2" stroke-linejoin="round"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 850 B |
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?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">
|
||||||
|
<title>icon/customer-nav/photos/white</title>
|
||||||
|
<g id="icon/customer-nav/photos/white" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<rect id="Rectangle" stroke="#FFFFFF" stroke-width="2" x="1" y="1" width="18" height="18" rx="1"></rect>
|
||||||
|
<circle id="Oval" stroke="#FFFFFF" stroke-width="2" cx="15" cy="5" r="1"></circle>
|
||||||
|
<polyline id="Path" stroke="#FFFFFF" stroke-width="2" stroke-linejoin="round" points="1 19 7 13 13 19"></polyline>
|
||||||
|
<path d="M13.3333333,14 L18,19 L13.3333333,19 L11,16.5 L13.3333333,14 Z" id="Combined-Shape" stroke="#FFFFFF" stroke-width="2" stroke-linejoin="round"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 850 B |
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?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">
|
||||||
|
<defs>
|
||||||
|
<circle id="path-1-right" cx="10" cy="10" r="10"></circle>
|
||||||
|
</defs>
|
||||||
|
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="pop-up/action/download-logs/date-range-copy-2" transform="translate(-232.000000, -187.000000)">
|
||||||
|
<g id="icon/sf-contain-b-copy-4" transform="translate(242.000000, 197.000000) scale(-1, 1) rotate(-270.000000) translate(-242.000000, -197.000000) translate(232.000000, 187.000000)">
|
||||||
|
<mask id="mask-2" fill="white">
|
||||||
|
<use xlink:href="#path-1-right"></use>
|
||||||
|
</mask>
|
||||||
|
<use id="Mask" fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-1-right"></use>
|
||||||
|
<g id="icon/sf-small/wizzard" mask="url(#mask-2)" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g transform="translate(6.666667, 6.000000)" id="Group">
|
||||||
|
<g>
|
||||||
|
<polyline id="Path-3" stroke="#1B2559" stroke-width="2" points="0 4.83333333 3.33333333 8.16666667 6.66666667 4.83333333"></polyline>
|
||||||
|
<line x1="3.33333333" y1="0.25" x2="3.33333333" y2="6.5" id="Path-4" stroke="#1B2559" stroke-width="2"></line>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -7,4 +7,7 @@ const transformNumber = value => (isValidNumber(value) ? value : null)
|
||||||
const defaultToZero = value =>
|
const defaultToZero = value =>
|
||||||
isValidNumber(parseInt(value)) ? parseInt(value) : 0
|
isValidNumber(parseInt(value)) ? parseInt(value) : 0
|
||||||
|
|
||||||
export { defaultToZero, transformNumber }
|
const numberToFiatAmount = value =>
|
||||||
|
value.toLocaleString('en-US', { maximumFractionDigits: 2 })
|
||||||
|
|
||||||
|
export { defaultToZero, transformNumber, numberToFiatAmount }
|
||||||
|
|
|
||||||
|
|
@ -3,85 +3,109 @@ import { getTimezoneOffset } from 'date-fns-tz'
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
|
|
||||||
const timezones = {
|
const timezones = {
|
||||||
'Pacific/Midway': 'Midway Island, Samoa',
|
'Pacific/Midway': { short: 'SST', long: 'Midway Island, Samoa' },
|
||||||
'Pacific/Honolulu': 'Hawaii',
|
'Pacific/Honolulu': { short: 'HAST', long: 'Hawaii' },
|
||||||
'America/Juneau': 'Alaska',
|
'America/Juneau': { short: 'AKST', long: 'Alaska' },
|
||||||
'America/Boise': 'Mountain Time',
|
'America/Boise': { short: 'MST', long: 'Mountain Time' },
|
||||||
'America/Dawson': 'Dawson, Yukon',
|
'America/Dawson': { short: 'MST', long: 'Dawson, Yukon' },
|
||||||
'America/Chihuahua': 'Chihuahua, La Paz, Mazatlan',
|
'America/Chihuahua': { short: null, long: 'Chihuahua, La Paz, Mazatlan' },
|
||||||
'America/Phoenix': 'Arizona',
|
'America/Phoenix': { short: 'MST', long: 'Arizona' },
|
||||||
'America/Chicago': 'Central Time',
|
'America/Chicago': { short: 'CST', long: 'Central Time' },
|
||||||
'America/Regina': 'Saskatchewan',
|
'America/Regina': { short: 'CST', long: 'Saskatchewan' },
|
||||||
'America/Mexico_City': 'Guadalajara, Mexico City, Monterrey',
|
'America/Mexico_City': {
|
||||||
'America/Belize': 'Central America',
|
short: 'CST',
|
||||||
'America/Detroit': 'Eastern Time',
|
long: 'Guadalajara, Mexico City, Monterrey'
|
||||||
'America/Bogota': 'Bogota, Lima, Quito',
|
},
|
||||||
'America/Caracas': 'Caracas, La Paz',
|
'America/Belize': { short: 'CST', long: 'Central America' },
|
||||||
'America/Santiago': 'Santiago',
|
'America/Detroit': { short: 'EST', long: 'Eastern Time' },
|
||||||
'America/St_Johns': 'Newfoundland and Labrador',
|
'America/Bogota': { short: 'COT', long: 'Bogota, Lima, Quito' },
|
||||||
'America/Sao_Paulo': 'Brasilia',
|
'America/Caracas': { short: 'VET', long: 'Caracas, La Paz' },
|
||||||
'America/Tijuana': 'Tijuana',
|
'America/Santiago': { short: 'CLST', long: 'Santiago' },
|
||||||
'America/Montevideo': 'Montevideo',
|
'America/St_Johns': { short: 'HNTN', long: 'Newfoundland and Labrador' },
|
||||||
'America/Argentina/Buenos_Aires': 'Buenos Aires, Georgetown',
|
'America/Sao_Paulo': { short: 'BRT', long: 'Brasilia' },
|
||||||
'America/Godthab': 'Greenland',
|
'America/Tijuana': { short: 'PST', long: 'Tijuana' },
|
||||||
'America/Los_Angeles': 'Pacific Time',
|
'America/Montevideo': { short: 'UYT', long: 'Montevideo' },
|
||||||
'Atlantic/Azores': 'Azores',
|
'America/Argentina/Buenos_Aires': {
|
||||||
'Atlantic/Cape_Verde': 'Cape Verde Islands',
|
short: null,
|
||||||
GMT: 'UTC',
|
long: 'Buenos Aires, Georgetown'
|
||||||
'Europe/London': 'Edinburgh, London',
|
},
|
||||||
'Europe/Dublin': 'Dublin',
|
'America/Godthab': { short: null, long: 'Greenland' },
|
||||||
'Europe/Lisbon': 'Lisbon',
|
'America/Los_Angeles': { short: 'PST', long: 'Pacific Time' },
|
||||||
'Africa/Casablanca': 'Casablanca, Monrovia',
|
'Atlantic/Azores': { short: 'AZOT', long: 'Azores' },
|
||||||
'Atlantic/Canary': 'Canary Islands',
|
'Atlantic/Cape_Verde': { short: 'CVT', long: 'Cape Verde Islands' },
|
||||||
'Europe/Belgrade': 'Belgrade, Bratislava, Budapest, Ljubljana, Prague',
|
GMT: { short: 'GMT', long: 'UTC' },
|
||||||
'Europe/Sarajevo': 'Sarajevo, Skopje, Warsaw, Zagreb',
|
'Europe/London': { short: 'GMT', long: 'Edinburgh, London' },
|
||||||
'Europe/Brussels': 'Brussels, Copenhagen, Madrid, Paris',
|
'Europe/Dublin': { short: 'GMT', long: 'Dublin' },
|
||||||
'Europe/Amsterdam': 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna',
|
'Europe/Lisbon': { short: 'WET', long: 'Lisbon' },
|
||||||
'Africa/Algiers': 'West Central Africa',
|
'Africa/Casablanca': { short: 'WET', long: 'Casablanca, Monrovia' },
|
||||||
'Europe/Bucharest': 'Bucharest',
|
'Atlantic/Canary': { short: 'WET', long: 'Canary Islands' },
|
||||||
'Africa/Cairo': 'Cairo',
|
'Europe/Belgrade': {
|
||||||
'Europe/Helsinki': 'Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius',
|
short: 'CET',
|
||||||
'Europe/Athens': 'Athens, Istanbul, Minsk',
|
long: 'Belgrade, Bratislava, Budapest, Ljubljana, Prague'
|
||||||
'Asia/Jerusalem': 'Jerusalem',
|
},
|
||||||
'Africa/Harare': 'Harare, Pretoria',
|
'Europe/Sarajevo': { short: 'CET', long: 'Sarajevo, Skopje, Warsaw, Zagreb' },
|
||||||
'Europe/Moscow': 'Moscow, St. Petersburg, Volgograd',
|
'Europe/Brussels': {
|
||||||
'Asia/Kuwait': 'Kuwait, Riyadh',
|
short: 'CET',
|
||||||
'Africa/Nairobi': 'Nairobi',
|
long: 'Brussels, Copenhagen, Madrid, Paris'
|
||||||
'Asia/Baghdad': 'Baghdad',
|
},
|
||||||
'Asia/Tehran': 'Tehran',
|
'Europe/Amsterdam': {
|
||||||
'Asia/Dubai': 'Abu Dhabi, Muscat',
|
short: 'CET',
|
||||||
'Asia/Baku': 'Baku, Tbilisi, Yerevan',
|
long: 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna'
|
||||||
'Asia/Kabul': 'Kabul',
|
},
|
||||||
'Asia/Yekaterinburg': 'Ekaterinburg',
|
'Africa/Algiers': { short: 'CET', long: 'West Central Africa' },
|
||||||
'Asia/Karachi': 'Islamabad, Karachi, Tashkent',
|
'Europe/Bucharest': { short: 'EET', long: 'Bucharest' },
|
||||||
'Asia/Kolkata': 'Chennai, Kolkata, Mumbai, New Delhi',
|
'Africa/Cairo': { short: 'EET', long: 'Cairo' },
|
||||||
'Asia/Kathmandu': 'Kathmandu',
|
'Europe/Helsinki': {
|
||||||
'Asia/Dhaka': 'Astana, Dhaka',
|
short: 'EET',
|
||||||
'Asia/Colombo': 'Sri Jayawardenepura',
|
long: 'Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius'
|
||||||
'Asia/Almaty': 'Almaty, Novosibirsk',
|
},
|
||||||
'Asia/Rangoon': 'Yangon Rangoon',
|
'Europe/Athens': { short: 'EET', long: 'Athens, Istanbul, Minsk' },
|
||||||
'Asia/Bangkok': 'Bangkok, Hanoi, Jakarta',
|
'Asia/Jerusalem': { short: 'IST', long: 'Jerusalem' },
|
||||||
'Asia/Krasnoyarsk': 'Krasnoyarsk',
|
'Africa/Harare': { short: 'CAT', long: 'Harare, Pretoria' },
|
||||||
'Asia/Shanghai': 'Beijing, Chongqing, Hong Kong SAR, Urumqi',
|
'Europe/Moscow': { short: 'MSK', long: 'Moscow, St. Petersburg, Volgograd' },
|
||||||
'Asia/Kuala_Lumpur': 'Kuala Lumpur, Singapore',
|
'Asia/Kuwait': { short: 'AST', long: 'Kuwait, Riyadh' },
|
||||||
'Asia/Taipei': 'Taipei',
|
'Africa/Nairobi': { short: 'EAT', long: 'Nairobi' },
|
||||||
'Australia/Perth': 'Perth',
|
'Asia/Baghdad': { short: 'AST', long: 'Baghdad' },
|
||||||
'Asia/Irkutsk': 'Irkutsk, Ulaanbaatar',
|
'Asia/Tehran': { short: 'IRST', long: 'Tehran' },
|
||||||
'Asia/Seoul': 'Seoul',
|
'Asia/Dubai': { short: 'GST', long: 'Abu Dhabi, Muscat' },
|
||||||
'Asia/Tokyo': 'Osaka, Sapporo, Tokyo',
|
'Asia/Baku': { short: 'AZT', long: 'Baku, Tbilisi, Yerevan' },
|
||||||
'Asia/Yakutsk': 'Yakutsk',
|
'Asia/Kabul': { short: 'AFT', long: 'Kabul' },
|
||||||
'Australia/Darwin': 'Darwin',
|
'Asia/Yekaterinburg': { short: 'YEKT', long: 'Ekaterinburg' },
|
||||||
'Australia/Adelaide': 'Adelaide',
|
'Asia/Karachi': { short: 'PKT', long: 'Islamabad, Karachi, Tashkent' },
|
||||||
'Australia/Sydney': 'Canberra, Melbourne, Sydney',
|
'Asia/Kolkata': { short: 'IST', long: 'Chennai, Kolkata, Mumbai, New Delhi' },
|
||||||
'Australia/Brisbane': 'Brisbane',
|
'Asia/Kathmandu': { short: null, long: 'Kathmandu' },
|
||||||
'Australia/Hobart': 'Hobart',
|
'Asia/Dhaka': { short: 'BST', long: 'Astana, Dhaka' },
|
||||||
'Asia/Vladivostok': 'Vladivostok',
|
'Asia/Colombo': { short: 'IST', long: 'Sri Jayawardenepura' },
|
||||||
'Pacific/Guam': 'Guam, Port Moresby',
|
'Asia/Almaty': { short: 'ALMT', long: 'Almaty, Novosibirsk' },
|
||||||
'Asia/Magadan': 'Magadan, Solomon Islands, New Caledonia',
|
'Asia/Rangoon': { short: null, long: 'Yangon Rangoon' },
|
||||||
'Asia/Kamchatka': 'Kamchatka, Marshall Islands',
|
'Asia/Bangkok': { short: 'ICT', long: 'Bangkok, Hanoi, Jakarta' },
|
||||||
'Pacific/Fiji': 'Fiji Islands',
|
'Asia/Krasnoyarsk': { short: 'KRAT', long: 'Krasnoyarsk' },
|
||||||
'Pacific/Auckland': 'Auckland, Wellington',
|
'Asia/Shanghai': {
|
||||||
'Pacific/Tongatapu': "Nuku'alofa"
|
short: 'CST',
|
||||||
|
long: 'Beijing, Chongqing, Hong Kong SAR, Urumqi'
|
||||||
|
},
|
||||||
|
'Asia/Kuala_Lumpur': { short: 'MYT', long: 'Kuala Lumpur, Singapore' },
|
||||||
|
'Asia/Taipei': { short: 'CST', long: 'Taipei' },
|
||||||
|
'Australia/Perth': { short: 'AWST', long: 'Perth' },
|
||||||
|
'Asia/Irkutsk': { short: 'IRKT', long: 'Irkutsk, Ulaanbaatar' },
|
||||||
|
'Asia/Seoul': { short: 'KST', long: 'Seoul' },
|
||||||
|
'Asia/Tokyo': { short: 'JST', long: 'Osaka, Sapporo, Tokyo' },
|
||||||
|
'Asia/Yakutsk': { short: 'YAKT', long: 'Yakutsk' },
|
||||||
|
'Australia/Darwin': { short: 'ACST', long: 'Darwin' },
|
||||||
|
'Australia/Adelaide': { short: 'ACDT', long: 'Adelaide' },
|
||||||
|
'Australia/Sydney': { short: 'AEDT', long: 'Canberra, Melbourne, Sydney' },
|
||||||
|
'Australia/Brisbane': { short: 'AEST', long: 'Brisbane' },
|
||||||
|
'Australia/Hobart': { short: 'AEDT', long: 'Hobart' },
|
||||||
|
'Asia/Vladivostok': { short: 'VLAT', long: 'Vladivostok' },
|
||||||
|
'Pacific/Guam': { short: 'ChST', long: 'Guam, Port Moresby' },
|
||||||
|
'Asia/Magadan': {
|
||||||
|
short: 'MAGT',
|
||||||
|
long: 'Magadan, Solomon Islands, New Caledonia'
|
||||||
|
},
|
||||||
|
'Asia/Kamchatka': { short: 'PETT', long: 'Kamchatka, Marshall Islands' },
|
||||||
|
'Pacific/Fiji': { short: 'FJT', long: 'Fiji Islands' },
|
||||||
|
'Pacific/Auckland': { short: 'NZDT', long: 'Auckland, Wellington' },
|
||||||
|
'Pacific/Tongatapu': { short: null, long: "Nuku'alofa" }
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildTzLabels = timezoneList => {
|
const buildTzLabels = timezoneList => {
|
||||||
|
|
@ -106,7 +130,7 @@ const buildTzLabels = timezoneList => {
|
||||||
const prefix = `(GMT${isNegative ? `-` : `+`}${hours}:${minutes})`
|
const prefix = `(GMT${isNegative ? `-` : `+`}${hours}:${minutes})`
|
||||||
|
|
||||||
acc.push({
|
acc.push({
|
||||||
label: `${prefix} - ${value[1]}`,
|
label: `${prefix} - ${value[1].long}`,
|
||||||
code: value[0]
|
code: value[0]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -117,4 +141,6 @@ const buildTzLabels = timezoneList => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default buildTzLabels(timezones)
|
const labels = buildTzLabels(timezones)
|
||||||
|
|
||||||
|
export { labels, timezones }
|
||||||
|
|
|
||||||