Merge remote-tracking branch 'upstream/release-7.5.0' into chore/merge-release-into-dev

This commit is contained in:
Taranto 2021-11-24 14:53:50 +00:00
commit 0ad2ee362a
109 changed files with 3283 additions and 697 deletions

1
.env.sample Normal file
View file

@ -0,0 +1 @@
LAMASSU_DB=DEV

1
.gitignore vendored
View file

@ -40,3 +40,4 @@ terraform.*
.terraform
db.json
.env

View file

@ -0,0 +1,34 @@
#!/usr/bin/env node
const _ = require('lodash')
const db = require('../lib/db')
if (process.argv.length !== 4) {
console.log('Usage: lamassu-update-cassettes <device_id> <number_of_cassettes>')
process.exit(1)
}
if (!_.isFinite(parseInt(process.argv[3]))) {
console.log('Error: <number_of_cassettes> is not a valid number (%s)', err)
process.exit(3)
}
if (parseInt(process.argv[3]) > 4 || parseInt(process.argv[3]) < 2) {
console.log('Error: <number_of_cassettes> is out of range. Should be a number between 2 and 4')
process.exit(3)
}
const deviceId = process.argv[2]
const numberOfCassettes = parseInt(process.argv[3])
const query = `UPDATE devices SET number_of_cassettes = $1 WHERE device_id = $2`
db.none(query, [numberOfCassettes, deviceId])
.then(() => {
console.log('Success! Device %s updated to %s cassettes', deviceId, numberOfCassettes)
process.exit(0)
})
.catch(err => {
console.log('Error: %s', err)
process.exit(3)
})

View file

@ -33,7 +33,7 @@ function batch () {
order by created desc limit $2`
const cashOutSql = `select 'cashOut' as tx_class, cash_out_txs.*,
(extract(epoch from (now() - greatest(created, confirmed_at))) * 1000) >= $2 as expired
(NOT dispense AND extract(epoch from (now() - greatest(created, confirmed_at))) >= $2) as expired
from cash_out_txs
order by created desc limit $1`
@ -51,7 +51,7 @@ function single (txId) {
where id=$2`
const cashOutSql = `select 'cashOut' as tx_class,
(extract(epoch from (now() - greatest(created, confirmed_at))) * 1000) >= $2 as expired,
(NOT dispense AND extract(epoch from (now() - greatest(created, confirmed_at))) >= $2) as expired,
cash_out_txs.*
from cash_out_txs
where id=$1`

View file

@ -1,16 +1,78 @@
const _ = require('lodash/fp')
const uuid = require('uuid')
// Custom algorith for two cassettes. For three or more denominations, we'll need
// to rethink this. Greedy algorithm fails to find *any* solution in some cases.
// Dynamic programming may be too inefficient for large amounts.
//
// We can either require canononical denominations for 3+, or try to expand
// this algorithm.
exports.makeChange = function makeChange (cassettes, amount) {
// Note: Everything here is converted to primitive numbers,
// since they're all integers, well within JS number range,
// and this is way more efficient in a tight loop.
const MAX_AMOUNT_OF_SOLUTIONS = 10000
const MAX_BRUTEFORCE_ITERATIONS = 10000000
function newSolution(cassettes, c0, c1, c2, c3, shouldFlip) {
return [
{
provisioned: shouldFlip ? cassettes[0].count - c0 : c0,
denomination: cassettes[0].denomination
},
{
provisioned: shouldFlip ? cassettes[1].count - c1 : c1,
denomination: cassettes[1].denomination
},
{
provisioned: shouldFlip ? cassettes[2].count - c2 : c2,
denomination: cassettes[2].denomination
},
{
provisioned: shouldFlip ? cassettes[3].count - c3 : c3,
denomination: cassettes[3].denomination
}
]
}
function mergeCassettes(cassettes) {
const map = {}
_.forEach(it => {
if (!map[it.denomination]) {
map[it.denomination] = 0
}
map[it.denomination] += it.count
}, cassettes)
return _.map(it => ({ denomination: it, count: map[it] }), _.keys(map))
}
function unmergeCassettes(cassettes, output) {
const map = {}
_.forEach(it => {
if (!map[it.denomination]) {
map[it.denomination] = 0
}
map[it.denomination] += it.provisioned
}, output)
const response = []
_.forEach(it => {
const value = {
denomination: it.denomination,
id: uuid.v4()
}
const amountNeeded = map[it.denomination]
if (!amountNeeded) {
return response.push({ provisioned: 0, ...value })
}
if (amountNeeded < it.count) {
map[it.denomination] = 0
return response.push({ provisioned: amountNeeded, ...value })
}
map[it.denomination] -= it.count
return response.push({ provisioned: it.count, ...value })
}, cassettes)
return response
}
function makeChangeDuo(cassettes, amount) {
const small = cassettes[0]
const large = cassettes[1]
@ -23,15 +85,140 @@ exports.makeChange = function makeChange (cassettes, amount) {
const remainder = amountNum - largeDenom * i
if (remainder % smallDenom !== 0) continue
const smallCount = remainder / smallDenom
if (smallCount > small.count) continue
return [
{provisioned: smallCount, denomination: small.denomination, id: uuid.v4()},
{provisioned: i, denomination: largeDenom, id: uuid.v4()}
{
provisioned: smallCount,
denomination: small.denomination,
id: uuid.v4()
},
{ provisioned: i, denomination: largeDenom, id: uuid.v4() }
]
}
return null
return []
}
function makeChange(outCassettes, amount) {
const available = _.reduce(
(res, val) => res + val.count * val.denomination,
0,
outCassettes
)
if (available < amount) {
console.log(`Tried to dispense more than was available for amount ${amount.toNumber()} with cassettes ${JSON.stringify(cassettes)}`)
return null
}
const cassettes = mergeCassettes(outCassettes)
const result =
_.size(cassettes) >= 3
? makeChangeDynamic(cassettes, amount, available)
: makeChangeDuo(cassettes, amount)
if (!result.length) return null
return unmergeCassettes(outCassettes, result)
}
function makeChangeDynamicBruteForce(outCassettes, amount, available) {
const solutions = []
let x = 0
const shouldFlip = amount > _.max(_.map(it => it.denomination * it.count, outCassettes))
const amountNum = shouldFlip ? available - amount : amount
const cassettes = shouldFlip ? _.reverse(outCassettes) : outCassettes
const { denomination: denomination0, count: count0 } = cassettes[0]
const { denomination: denomination1, count: count1 } = cassettes[1]
const { denomination: denomination2, count: count2 } = cassettes[2]
const { denomination: denomination3, count: count3 } = cassettes[3]
const startTime = new Date().getTime()
loop1: for (let i = 0; i <= count0; i++) {
const firstSum = i * denomination0
for (let j = 0; j <= count1; j++) {
const secondSum = firstSum + j * denomination1
if (secondSum > amountNum) break
if (secondSum === amountNum) {
solutions.push(newSolution(cassettes, i, j, 0, 0, shouldFlip))
}
for (let k = 0; k <= count2; k++) {
const thirdSum = secondSum + k * denomination2
if (thirdSum > amountNum) break
if (denomination2 === 0) break
if (thirdSum === amountNum) {
solutions.push(newSolution(cassettes, i, j, k, 0, shouldFlip))
}
for (let l = 0; l <= count3; l++) {
if ((x > MAX_AMOUNT_OF_SOLUTIONS && solutions.length >= 1) || x > MAX_BRUTEFORCE_ITERATIONS) break loop1
x++
const fourthSum = thirdSum + l * denomination3
if (fourthSum > amountNum) break
if (denomination3 === 0) break
if (fourthSum === amountNum) {
solutions.push(newSolution(cassettes, i, j, k, l, shouldFlip))
}
}
}
}
}
const endTime = new Date().getTime()
console.log(`Exiting bruteforce after ${x} tries. Took ${endTime - startTime} ms`)
return solutions
}
function makeChangeDynamic(cassettes, amount, available) {
while (_.size(cassettes) < 4) {
cassettes.push({ denomination: 0, count: 0 })
}
const amountNum = amount.toNumber()
const solutions = makeChangeDynamicBruteForce(cassettes, amountNum, available)
const sortedSolutions = _.sortBy(it => {
const arr = []
for (let la = 0; la < 4; la++) {
arr.push(cassettes[la].count - it[la].provisioned)
}
if (arr.length < 2) return Infinity
return _.max(arr) - _.min(arr)
}, solutions)
const cleanSolution = _.filter(
it => it.denomination > 0,
_.head(sortedSolutions)
)
const response = cleanSolution
// Final sanity check
let total = 0
_.forEach(it => {
total += it.provisioned * it.denomination
}, response)
if (total === amountNum) return response
console.log(
`Failed to find a solution for ${amountNum} with cassettes ${JSON.stringify(cassettes)}`
)
return []
}
module.exports = { makeChange }

View file

@ -25,16 +25,16 @@ const BINARIES = {
BTC: {
defaultUrl: 'https://bitcoincore.org/bin/bitcoin-core-0.20.0/bitcoin-0.20.0-x86_64-linux-gnu.tar.gz',
defaultDir: 'bitcoin-0.20.0/bin',
url: 'https://bitcoincore.org/bin/bitcoin-core-0.21.0/bitcoin-0.21.0-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-0.21.0/bin'
url: 'https://bitcoincore.org/bin/bitcoin-core-22.0/bitcoin-22.0-x86_64-linux-gnu.tar.gz',
dir: 'bitcoin-22.0/bin'
},
ETH: {
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.8-26675454.tar.gz',
dir: 'geth-linux-amd64-1.10.8-26675454'
url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.12-6c4dc6c3.tar.gz',
dir: 'geth-linux-amd64-1.10.12-6c4dc6c3'
},
ZEC: {
url: 'https://z.cash/downloads/zcash-4.4.1-linux64-debian-stretch.tar.gz',
dir: 'zcash-4.4.1/bin'
url: 'https://z.cash/downloads/zcash-4.5.1-1-linux64-debian-stretch.tar.gz',
dir: 'zcash-4.5.1-1/bin'
},
DASH: {
url: 'https://github.com/dashpay/dash/releases/download/v0.17.0.3/dashcore-0.17.0.3-x86_64-linux-gnu.tar.gz',
@ -83,6 +83,8 @@ autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/${cryptoCode}${isWallet ? `-wallet` : ``}.err.log
stdout_logfile=/var/log/supervisor/${cryptoCode}${isWallet ? `-wallet` : ``}.out.log
stderr_logfile_backups=2
stdout_logfile_backups=2
environment=HOME="/root"
`
}

View file

@ -26,6 +26,6 @@ function updateCore (coinRec, isCurrentlyRunning) {
function setup (dataDir) {
const coinRec = coinUtils.getCryptoCurrency('ETH')
common.firewall([coinRec.defaultPort])
const cmd = `/usr/local/bin/${coinRec.daemon} --datadir "${dataDir}" --syncmode="light" --cache 2048 --maxpeers 40 --rpc`
const cmd = `/usr/local/bin/${coinRec.daemon} --datadir "${dataDir}" --syncmode="light" --cache 2048 --maxpeers 40 --http`
common.writeSupervisorConfig(coinRec, cmd)
}

View file

@ -23,8 +23,8 @@ const PLUGINS = {
DASH: require('./dash.js'),
ETH: require('./ethereum.js'),
LTC: require('./litecoin.js'),
ZEC: require('./zcash.js'),
XMR: require('./monero.js')
XMR: require('./monero.js'),
ZEC: require('./zcash.js')
}
module.exports = {run}

View file

@ -37,14 +37,14 @@ function mapDispense (tx) {
if (_.isEmpty(bills)) return {}
return {
provisioned_1: bills[0].provisioned,
provisioned_2: bills[1].provisioned,
dispensed_1: bills[0].dispensed,
dispensed_2: bills[1].dispensed,
rejected_1: bills[0].rejected,
rejected_2: bills[1].rejected,
denomination_1: bills[0].denomination,
denomination_2: bills[1].denomination
}
const res = {}
_.forEach(it => {
res[`provisioned_${it + 1}`] = bills[it].provisioned
res[`denomination_${it + 1}`] = bills[it].denomination
res[`dispensed_${it + 1}`] = bills[it].dispensed
res[`rejected_${it + 1}`] = bills[it].rejected
}, _.times(_.identity(), _.size(bills)))
return res
}

View file

@ -108,16 +108,24 @@ function updateCassettes (t, tx) {
if (!dispenseOccurred(tx.bills)) return Promise.resolve()
const sql = `update devices set
cassette1 = cassette1 - $1,
cassette2 = cassette2 - $2
where device_id = $3
returning cassette1, cassette2`
${_.size(tx.bills) > 0 ? `cassette1 = cassette1 - $1` : ``}
${_.size(tx.bills) > 1 ? `, cassette2 = cassette2 - $2` : ``}
${_.size(tx.bills) > 2 ? `, cassette3 = cassette3 - $3` : ``}
${_.size(tx.bills) > 3 ? `, cassette4 = cassette4 - $4` : ``}
where device_id = $${_.size(tx.bills) + 1}
returning
${_.size(tx.bills) > 0 ? `cassette1` : ``}
${_.size(tx.bills) > 1 ? `, cassette2`: ``}
${_.size(tx.bills) > 2 ? `, cassette3` : ``}
${_.size(tx.bills) > 3 ? `, cassette4` : ``}`
const values = [
tx.bills[0].dispensed + tx.bills[0].rejected,
tx.bills[1].dispensed + tx.bills[1].rejected,
tx.deviceId
]
const values = []
_.forEach(it => values.push(
tx.bills[it].dispensed + tx.bills[it].rejected
), _.times(_.identity(), _.size(tx.bills)))
values.push(tx.deviceId)
return t.one(sql, values)
.then(r => socket.emit(_.assign(r, {op: 'cassetteUpdate', deviceId: tx.deviceId})))

View file

@ -4,7 +4,9 @@ const db = require('../db')
const T = require('../time')
const BN = require('../bn')
const REDEEMABLE_AGE = T.day
// FP operations on Postgres result in very big errors.
// E.g.: 1853.013808 * 1000 = 1866149.494
const REDEEMABLE_AGE = T.day / 1000
const CASH_OUT_TRANSACTION_STATES = `
case
@ -47,12 +49,18 @@ function addDbBills (tx) {
const bills = tx.bills
if (_.isEmpty(bills)) return tx
return _.assign(tx, {
provisioned1: bills[0].provisioned,
provisioned2: bills[1].provisioned,
denomination1: bills[0].denomination,
denomination2: bills[1].denomination
})
const billsObj = {
provisioned1: bills[0]?.provisioned ?? 0,
provisioned2: bills[1]?.provisioned ?? 0,
provisioned3: bills[2]?.provisioned ?? 0,
provisioned4: bills[3]?.provisioned ?? 0,
denomination1: bills[0]?.denomination ?? 0,
denomination2: bills[1]?.denomination ?? 0,
denomination3: bills[2]?.denomination ?? 0,
denomination4: bills[3]?.denomination ?? 0
}
return _.assign(tx, billsObj)
}
function toDb (tx) {
@ -84,12 +92,12 @@ function toObj (row) {
newObj.direction = 'cashOut'
const billFields = ['denomination1', 'denomination2', 'provisioned1', 'provisioned2']
const billFields = ['denomination1', 'denomination2', 'denomination3', 'denomination4', 'provisioned1', 'provisioned2', 'provisioned3', 'provisioned4']
if (_.every(_.isNil, _.at(billFields, newObj))) return newObj
if (_.some(_.isNil, _.at(billFields, newObj))) throw new Error('Missing cassette values')
const bills = [
const billFieldsArr = [
{
denomination: newObj.denomination1,
provisioned: newObj.provisioned1
@ -97,9 +105,21 @@ function toObj (row) {
{
denomination: newObj.denomination2,
provisioned: newObj.provisioned2
},
{
denomination: newObj.denomination3,
provisioned: newObj.provisioned3
},
{
denomination: newObj.denomination4,
provisioned: newObj.provisioned4
}
]
// There can't be bills with denomination === 0.
// If a bill has denomination === 0, then that cassette is not set and should be filtered out.
const bills = _.filter(it => it.denomination > 0, billFieldsArr)
return _.set('bills', bills, _.omit(billFields, newObj))
}
@ -109,7 +129,7 @@ function redeemableTxs (deviceId) {
and redeem=$2
and dispense=$3
and provisioned_1 is not null
and (extract(epoch from (now() - greatest(created, confirmed_at))) * 1000) < $4`
and extract(epoch from (now() - greatest(created, confirmed_at))) < $4`
return db.any(sql, [deviceId, true, false, REDEEMABLE_AGE])
.then(_.map(toObj))

View file

@ -63,17 +63,12 @@ function postProcess (txVector, justAuthorized, pi) {
return bills
})
.then(bills => {
const provisioned1 = bills[0].provisioned
const provisioned2 = bills[1].provisioned
const denomination1 = bills[0].denomination
const denomination2 = bills[1].denomination
const rec = {}
const rec = {
provisioned_1: provisioned1,
provisioned_2: provisioned2,
denomination_1: denomination1,
denomination_2: denomination2
}
_.forEach(it => {
rec[`provisioned_${it + 1}`] = bills[it].provisioned
rec[`denomination_${it + 1}`] = bills[it].denomination
}, _.times(_.identity(), _.size(bills)))
return cashOutActions.logAction(db, 'provisionNotes', rec, newTx)
.then(_.constant({ bills }))

View file

@ -23,6 +23,9 @@ function getMachines () {
cashbox: r.cashbox,
cassette1: r.cassette1,
cassette2: r.cassette2,
cassette3: r.cassette3,
cassette4: r.cassette4,
numberOfCassettes: r.number_of_cassettes,
version: r.version,
model: r.model,
pairedAt: new Date(r.created),
@ -74,21 +77,7 @@ function getMachineNames (config) {
const mergeByDeviceId = (x, y) => _.values(_.merge(_.keyBy('deviceId', x), _.keyBy('deviceId', y)))
const machines = mergeByDeviceId(mergeByDeviceId(rawMachines, heartbeat), performance)
const addName = r => {
const cashOutConfig = configManager.getCashOut(r.deviceId, config)
const cashOut = !!cashOutConfig.active
const statuses = [
getStatus(
_.first(pings[r.deviceId]),
_.first(checkStuckScreen(events, r.name))
)
]
return _.assign(r, { cashOut, statuses })
}
return _.map(addName, machines)
return machines.map(addName(pings, events, config))
})
}
@ -115,6 +104,9 @@ function getMachine (machineId, config) {
cashbox: r.cashbox,
cassette1: r.cassette1,
cassette2: r.cassette2,
cassette3: r.cassette3,
cassette4: r.cassette4,
numberOfCassettes: r.number_of_cassettes,
version: r.version,
model: r.model,
pairedAt: new Date(r.created),
@ -127,7 +119,7 @@ function getMachine (machineId, config) {
.then(([machine, events, config]) => {
const pings = checkPings([machine])
return [machine].map(addName(pings, events, config))[0]
return addName(pings, events, config)(machine)
})
}
@ -138,8 +130,8 @@ function renameMachine (rec) {
function resetCashOutBills (rec) {
const detailB = notifierUtils.buildDetail({ deviceId: rec.deviceId })
const sql = `UPDATE devices SET cassette1=$1, cassette2=$2 WHERE device_id=$3;`
return db.none(sql, [rec.cassettes[0], rec.cassettes[1], rec.deviceId]).then(() => notifierQueries.invalidateNotification(detailB, 'fiatBalance'))
const sql = `UPDATE devices SET cassette1=$1, cassette2=$2, cassette3=$3, cassette4=$4 WHERE device_id=$5;`
return db.none(sql, [rec.cassettes[0], rec.cassettes[1], rec.cassettes[2], rec.cassettes[3], rec.deviceId]).then(() => notifierQueries.invalidateNotification(detailB, 'fiatBalance'))
}
function emptyCashInBills (rec) {
@ -148,8 +140,8 @@ function emptyCashInBills (rec) {
}
function setCassetteBills (rec) {
const sql = 'update devices set cashbox=$1, cassette1=$2, cassette2=$3 where device_id=$4'
return db.none(sql, [rec.cashbox, rec.cassettes[0], rec.cassettes[1], rec.deviceId])
const sql = 'update devices set cashbox=$1, cassette1=$2, cassette2=$3, cassette3=$4, cassette4=$5 where device_id=$6'
return db.none(sql, [rec.cashbox, rec.cassettes[0], rec.cassettes[1], rec.cassettes[2], rec.cassettes[3], rec.deviceId])
}
function unpair (rec) {

View file

@ -1,6 +1,8 @@
const _ = require('lodash/fp')
const crypto = require('crypto')
const logger = require('../logger')
function sha256 (buf) {
const hash = crypto.createHash('sha256')
@ -9,6 +11,7 @@ function sha256 (buf) {
}
const populateDeviceId = function (req, res, next) {
logger.info(`DEBUG LOG - Method: ${req.method} Path: ${req.path}`)
const deviceId = _.isFunction(req.connection.getPeerCertificate)
? sha256(req.connection.getPeerCertificate().raw)
: null

View file

@ -18,7 +18,7 @@ const resolvers = {
machine: (...[, { deviceId }]) => machineLoader.getMachine(deviceId)
},
Mutation: {
machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, newName }, context]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, newName }, context)
machineAction: (...[, { deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context]) => machineAction({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }, context)
}
}

View file

@ -17,6 +17,9 @@ const typeDef = gql`
cashbox: Int
cassette1: Int
cassette2: Int
cassette3: Int
cassette4: Int
numberOfCassettes: Int
statuses: [MachineStatus]
latestEvent: MachineEvent
downloadSpeed: String
@ -51,7 +54,7 @@ const typeDef = gql`
}
type Mutation {
machineAction(deviceId:ID!, action: MachineAction!, cashbox: Int, cassette1: Int, cassette2: Int, newName: String): Machine @auth
machineAction(deviceId:ID!, action: MachineAction!, cashbox: Int, cassette1: Int, cassette2: Int, cassette3: Int, cassette4: Int, newName: String): Machine @auth
}
`

View file

@ -6,14 +6,14 @@ function getMachine (machineId) {
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
}
function machineAction ({ deviceId, action, cashbox, cassette1, cassette2, newName }, context) {
function machineAction ({ deviceId, action, cashbox, cassette1, cassette2, cassette3, cassette4, newName }) {
const operatorId = context.res.locals.operatorId
return getMachine(deviceId)
.then(machine => {
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
return machine
})
.then(machineLoader.setMachine({ deviceId, action, cashbox, cassettes: [cassette1, cassette2], newName }, operatorId))
.then(machineLoader.setMachine({ deviceId, action, cashbox, cassettes: [cassette1, cassette2, cassette3, cassette4], newName }, operatorId))
.then(getMachine(deviceId))
}

View file

@ -82,7 +82,7 @@ function batch (
c.id_card_photo_path AS customer_id_card_photo_path,
txs.tx_customer_photo_at AS tx_customer_photo_at,
txs.tx_customer_photo_path AS tx_customer_photo_path,
(extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $1 AS expired
(NOT txs.dispense AND extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) >= $1) AS expired
FROM (SELECT *, ${CASH_OUT_TRANSACTION_STATES} AS txStatus FROM cash_out_txs) txs
INNER JOIN cash_out_actions actions ON txs.id = actions.tx_id
AND actions.action = 'provisionAddress'
@ -186,7 +186,7 @@ function getCustomerTransactionsBatch (ids) {
c.name AS customer_name,
c.front_camera_path AS customer_front_camera_path,
c.id_card_photo_path AS customer_id_card_photo_path,
(extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $3 AS expired
(NOT txs.dispense AND extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) >= $3) AS expired
FROM cash_out_txs txs
INNER JOIN cash_out_actions actions ON txs.id = actions.tx_id
AND actions.action = 'provisionAddress'
@ -230,7 +230,7 @@ function single (txId) {
c.front_camera_path AS customer_front_camera_path,
c.id_card_photo_path AS customer_id_card_photo_path,
(extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $2 AS expired
(NOT txs.dispense AND extract(epoch FROM (now() - greatest(txs.created, txs.confirmed_at))) >= $2) AS expired
FROM cash_out_txs txs
INNER JOIN cash_out_actions actions ON txs.id = actions.tx_id
AND actions.action = 'provisionAddress'

View file

@ -55,6 +55,7 @@ const getGlobalLocale = it => getLocale(null, it)
const getWalletSettings = (key, it) => _.compose(fromNamespace(key), fromNamespace(namespaces.WALLETS))(it)
const getCashOut = (key, it) => _.compose(fromNamespace(key), fromNamespace(namespaces.CASH_OUT))(it)
const getGlobalCashOut = fromNamespace(namespaces.CASH_OUT)
const getOperatorInfo = fromNamespace(namespaces.OPERATOR_INFO)
const getCoinAtmRadar = fromNamespace(namespaces.COIN_ATM_RADAR)
const getTermsConditions = fromNamespace(namespaces.TERMS_CONDITIONS)
@ -152,6 +153,7 @@ module.exports = {
getAllCryptoCurrencies,
getTriggers,
getTriggersAutomation,
getGlobalCashOut,
getCashOut,
getCryptosFromWalletNamespace,
getCryptoUnits

View file

@ -6,12 +6,12 @@ const _ = require('lodash/fp')
require('dotenv').config()
const DATABASE = process.env.LAMASSU_DB ?? 'DEV'
const DATABASE = process.env.LAMASSU_DB ?? 'PROD'
const dbMapping = psqlConf => ({
STRESS_TEST: _.replace('lamassu', 'lamassu_stress', psqlConf),
RELEASE: _.replace('lamassu', 'lamassu_release', psqlConf),
DEV: _.replace('lamassu', 'lamassu', psqlConf)
DEV: _.replace('lamassu', 'lamassu', psqlConf),
PROD: _.replace('lamassu', 'lamassu', psqlConf)
})
/**

View file

@ -5,6 +5,10 @@ const db = require('./db')
const options = require('./options')
const logger = require('./logger')
// A machine on an older version (no multicassette code) could be paired with a server with multicassette code.
// This makes sure that the server stores a default value
const DEFAULT_NUMBER_OF_CASSETTES = 2
function pullToken (token) {
const sql = `delete from pairing_tokens
where token=$1
@ -13,23 +17,26 @@ function pullToken (token) {
}
function unpair (deviceId) {
const sql = 'delete from devices where device_id=$1'
const deleteMachinePings = 'delete from machine_pings where device_id=$1'
// TODO new-admin: We should remove all configs related to that device. This can get tricky.
return Promise.all([db.none(sql, [deviceId]), db.none(deleteMachinePings, [deviceId])])
return db.tx(t => {
const q1 = t.none('DELETE FROM devices WHERE device_id=$1', [deviceId])
const q2 = t.none('DELETE FROM machine_pings WHERE device_id=$1', [deviceId])
const q3 = t.none('DELETE FROM machine_network_heartbeat WHERE device_id=$1', [deviceId])
const q4 = t.none('DELETE FROM machine_network_performance WHERE device_id=$1', [deviceId])
return Promise.all([q1, q2, q3, q4])
})
}
function pair (token, deviceId, machineModel) {
function pair (token, deviceId, machineModel, numOfCassettes = DEFAULT_NUMBER_OF_CASSETTES) {
return pullToken(token)
.then(r => {
if (r.expired) return false
const insertSql = `insert into devices (device_id, name) values ($1, $2)
const insertSql = `insert into devices (device_id, name, number_of_cassettes) values ($1, $2, $3)
on conflict (device_id)
do update set paired=TRUE, display=TRUE`
return db.none(insertSql, [deviceId, r.name])
return db.none(insertSql, [deviceId, r.name, numOfCassettes])
.then(() => true)
})
.catch(err => {

View file

@ -112,8 +112,10 @@ function plugins (settings, deviceId) {
if (_.isEmpty(redeemableTxs)) return cassettes
const sumTxs = (sum, tx) => {
const bills = tx.bills
const sameDenominations = a => a[0].denomination === a[1].denomination
// cash-out-helper sends 0 as fallback value, need to filter it out as there are no '0' denominations
const bills = _.filter(it => it.denomination > 0, tx.bills)
const sameDenominations = a => a[0]?.denomination === a[1]?.denomination
const doDenominationsMatch = _.every(sameDenominations, _.zip(cassettes, bills))
if (!doDenominationsMatch) {
@ -123,7 +125,7 @@ function plugins (settings, deviceId) {
return _.map(r => r[0] + r[1].provisioned, _.zip(sum, tx.bills))
}
const provisioned = _.reduce(sumTxs, [0, 0], redeemableTxs)
const provisioned = _.reduce(sumTxs, _.times(_.constant(0), _.size(cassettes)), redeemableTxs)
const zipped = _.zip(_.map('count', cassettes), provisioned)
const counts = _.map(r => r[0] - r[1], zipped)
@ -131,16 +133,15 @@ function plugins (settings, deviceId) {
throw new Error('Negative note count: %j', counts)
}
return [
{
denomination: cassettes[0].denomination,
count: counts[0]
},
{
denomination: cassettes[1].denomination,
count: counts[1]
}
]
const computedCassettes = []
_.forEach(it => {
computedCassettes.push({
denomination: cassettes[it].denomination,
count: counts[it]
})
}, _.times(_.identity(), _.size(cassettes)))
return computedCassettes
}
function buildAvailableCassettes (excludeTxId) {
@ -148,28 +149,32 @@ function plugins (settings, deviceId) {
if (!cashOutConfig.active) return Promise.resolve()
const denominations = [cashOutConfig.top, cashOutConfig.bottom]
const virtualCassettes = [Math.max(cashOutConfig.top, cashOutConfig.bottom) * 2]
return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)])
.then(([rec, _redeemableTxs]) => {
const redeemableTxs = _.reject(_.matchesProperty('id', excludeTxId), _redeemableTxs)
const denominations = []
_.forEach(it => {
denominations.push(cashOutConfig[`cassette${it + 1}`])
}, _.times(_.identity(), rec.numberOfCassettes))
const virtualCassettes = [Math.max(...denominations) * 2]
const counts = argv.cassettes
? argv.cassettes.split(',')
: rec.counts
const cassettes = [
{
denomination: parseInt(denominations[0], 10),
count: parseInt(counts[0], 10)
},
{
denomination: parseInt(denominations[1], 10),
count: parseInt(counts[1], 10)
}
]
if (rec.counts.length !== denominations.length) {
throw new Error('Denominations and respective counts do not match!')
}
const cassettes = []
_.forEach(it => {
cassettes.push({
denomination: parseInt(denominations[it], 10),
count: parseInt(counts[it], 10)
})
}, _.times(_.identity(), rec.numberOfCassettes))
try {
return {
@ -320,7 +325,7 @@ function plugins (settings, deviceId) {
function dispenseAck (tx) {
const cashOutConfig = configManager.getCashOut(deviceId, settings.config)
const cassettes = [cashOutConfig.top, cashOutConfig.bottom]
const cassettes = [cashOutConfig.cassette1, cashOutConfig.cassette2, cashOutConfig.cassette3, cashOutConfig.cassette4]
return dbm.addDispense(deviceId, tx, cassettes)
}
@ -614,8 +619,10 @@ function plugins (settings, deviceId) {
function checkDeviceCashBalances (fiatCode, device) {
const cashOutConfig = configManager.getCashOut(device.deviceId, settings.config)
const denomination1 = cashOutConfig.top
const denomination2 = cashOutConfig.bottom
const denomination1 = cashOutConfig.cassette1
const denomination2 = cashOutConfig.cassette2
const denomination3 = cashOutConfig.cassette3
const denomination4 = cashOutConfig.cassette4
const cashOutEnabled = cashOutConfig.active
const isCassetteLow = (have, max, limit) => cashOutEnabled && ((have / max) * 100) < limit
@ -632,7 +639,7 @@ function plugins (settings, deviceId) {
}
: null
const cassette1Alert = isCassetteLow(device.cassette1, cassetteMaxCapacity, notifications.fillingPercentageCassette1)
const cassette1Alert = device.numberOfCassettes >= 1 && isCassetteLow(device.cassette1, cassetteMaxCapacity, notifications.fillingPercentageCassette1)
? {
code: 'LOW_CASH_OUT',
cassette: 1,
@ -644,7 +651,7 @@ function plugins (settings, deviceId) {
}
: null
const cassette2Alert = isCassetteLow(device.cassette2, cassetteMaxCapacity, notifications.fillingPercentageCassette2)
const cassette2Alert = device.numberOfCassettes >= 2 && isCassetteLow(device.cassette2, cassetteMaxCapacity, notifications.fillingPercentageCassette2)
? {
code: 'LOW_CASH_OUT',
cassette: 2,
@ -656,7 +663,31 @@ function plugins (settings, deviceId) {
}
: null
return _.compact([cashInAlert, cassette1Alert, cassette2Alert])
const cassette3Alert = device.numberOfCassettes >= 3 && isCassetteLow(device.cassette3, cassetteMaxCapacity, notifications.fillingPercentageCassette3)
? {
code: 'LOW_CASH_OUT',
cassette: 3,
machineName,
deviceId: device.deviceId,
notes: device.cassette3,
denomination: denomination3,
fiatCode
}
: null
const cassette4Alert = device.numberOfCassettes >= 4 && isCassetteLow(device.cassette4, cassetteMaxCapacity, notifications.fillingPercentageCassette4)
? {
code: 'LOW_CASH_OUT',
cassette: 4,
machineName,
deviceId: device.deviceId,
notes: device.cassette4,
denomination: denomination4,
fiatCode
}
: null
return _.compact([cashInAlert, cassette1Alert, cassette2Alert, cassette3Alert, cassette4Alert])
}
function checkCryptoBalances (fiatCode, devices) {

View file

@ -98,3 +98,17 @@ function parseConf (confPath) {
return res
}
function rpcConfig (cryptoRec) {
try {
const configPath = coinUtils.configPath(cryptoRec)
const config = parseConf(configPath)
return {
username: config.rpcuser,
password: config.rpcpassword,
port: config.rpcport || cryptoRec.defaultPort
}
} catch (err) {
throw new Error('Wallet is currently not installed')
}
}

View file

@ -8,24 +8,11 @@ const logger = require('../../../logger')
const { utils: coinUtils } = require('lamassu-coins')
const cryptoRec = coinUtils.getCryptoCurrency('BTC')
const configPath = coinUtils.configPath(cryptoRec, options.blockchainDir)
const unitScale = cryptoRec.unitScale
function rpcConfig () {
try {
const config = jsonRpc.parseConf(configPath)
return {
username: config.rpcuser,
password: config.rpcpassword,
port: config.rpcport || cryptoRec.defaultPort
}
} catch (err) {
throw new Error('wallet is currently not installed')
}
}
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
function fetch (method, params) {
return jsonRpc.fetch(rpcConfig(), method, params)
return jsonRpc.fetch(rpcConfig, method, params)
}
function checkCryptoCode (cryptoCode) {

View file

@ -8,24 +8,11 @@ const BN = require('../../../bn')
const E = require('../../../error')
const cryptoRec = coinUtils.getCryptoCurrency('DASH')
const configPath = coinUtils.configPath(cryptoRec, options.blockchainDir)
const unitScale = cryptoRec.unitScale
function rpcConfig () {
try {
const config = jsonRpc.parseConf(configPath)
return {
username: config.rpcuser,
password: config.rpcpassword,
port: config.rpcport || cryptoRec.defaultPort
}
} catch (err) {
throw new Error('wallet is currently not installed')
}
}
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
function fetch (method, params) {
return jsonRpc.fetch(rpcConfig(), method, params)
return jsonRpc.fetch(rpcConfig, method, params)
}
function checkCryptoCode (cryptoCode) {

View file

@ -8,23 +8,11 @@ const BN = require('../../../bn')
const E = require('../../../error')
const cryptoRec = coinUtils.getCryptoCurrency('LTC')
const configPath = coinUtils.configPath(cryptoRec, options.blockchainDir)
const unitScale = cryptoRec.unitScale
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
function rpcConfig () {
try {
const config = jsonRpc.parseConf(configPath)
return {
username: config.rpcuser,
password: config.rpcpassword,
port: config.rpcport || cryptoRec.defaultPort
}
} catch (err) {
throw new Error('wallet is currently not installed')
}
}
function fetch (method, params) {
return jsonRpc.fetch(rpcConfig(), method, params)
return jsonRpc.fetch(rpcConfig, method, params)
}
function checkCryptoCode (cryptoCode) {

View file

@ -9,24 +9,11 @@ const BN = require('../../../bn')
const E = require('../../../error')
const cryptoRec = coinUtils.getCryptoCurrency('ZEC')
const configPath = coinUtils.configPath(cryptoRec, options.blockchainDir)
const unitScale = cryptoRec.unitScale
function rpcConfig () {
try {
const config = jsonRpc.parseConf(configPath)
return {
username: config.rpcuser,
password: config.rpcpassword,
port: config.rpcport || cryptoRec.defaultPort
}
} catch (err) {
throw new Error('wallet is currently not installed')
}
}
const rpcConfig = jsonRpc.rpcConfig(cryptoRec)
function fetch (method, params) {
return jsonRpc.fetch(rpcConfig(), method, params)
return jsonRpc.fetch(rpcConfig, method, params)
}
function checkCryptoCode (cryptoCode) {

View file

@ -28,7 +28,7 @@ const TRADE_INTERVAL = 60 * T.seconds
const PONG_INTERVAL = 10 * T.seconds
const LOGS_CLEAR_INTERVAL = 1 * T.day
const SANCTIONS_INITIAL_DOWNLOAD_INTERVAL = 5 * T.minutes
const SANCTIONS_UPDATE_INTERVAL = 1 * T.week
const SANCTIONS_UPDATE_INTERVAL = 1 * T.day
const RADAR_UPDATE_INTERVAL = 5 * T.minutes
const PRUNE_MACHINES_HEARTBEAT = 1 * T.day

View file

@ -26,13 +26,17 @@ exports.recordDeviceEvent = function recordDeviceEvent (deviceId, event) {
}
exports.cassetteCounts = function cassetteCounts (deviceId) {
const sql = 'SELECT cassette1, cassette2 FROM devices ' +
const sql = 'SELECT cassette1, cassette2, cassette3, cassette4, number_of_cassettes FROM devices ' +
'WHERE device_id=$1'
return db.one(sql, [deviceId])
.then(row => {
const counts = [row.cassette1, row.cassette2]
return {counts}
const counts = []
_.forEach(it => {
counts.push(row[`cassette${it + 1}`])
}, _.times(_.identity(), row.number_of_cassettes))
return { numberOfCassettes: row.number_of_cassettes, counts }
})
}

View file

@ -10,8 +10,9 @@ function pair (req, res, next) {
const token = req.query.token
const deviceId = req.deviceId
const model = req.query.model
const numOfCassettes = req.query.numOfCassettes
return pairing.pair(token, deviceId, model)
return pairing.pair(token, deviceId, model, numOfCassettes)
.then(isValid => {
if (isValid) return res.json({ status: 'paired' })
throw httpError('Pairing failed')

View file

@ -11,6 +11,20 @@ const semver = require('semver')
const state = require('../middlewares/state')
const version = require('../../package.json').version
const urlsToPing = [
`us.archive.ubuntu.com`,
`uk.archive.ubuntu.com`,
`za.archive.ubuntu.com`,
`cn.archive.ubuntu.com`
]
const speedtestFiles = [
{
url: 'https://github.com/lamassu/speed-test-assets/raw/main/python-defaults_2.7.18-3.tar.gz',
size: 44668
}
]
function checkHasLightning (settings) {
return configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2'
}
@ -86,7 +100,9 @@ function poll (req, res, next) {
operatorInfo,
machineInfo,
triggers,
triggersAutomation
triggersAutomation,
speedtestFiles,
urlsToPing
}
// BACKWARDS_COMPATIBILITY 7.6

View file

@ -5,7 +5,9 @@ const CashInTx = require('./cash-in/cash-in-tx')
const CashOutTx = require('./cash-out/cash-out-tx')
const T = require('./time')
const REDEEMABLE_AGE = T.day
// FP operations on Postgres result in very big errors.
// E.g.: 1853.013808 * 1000 = 1866149.494
const REDEEMABLE_AGE = T.day / 1000
function process (tx, pi) {
const mtx = massage(tx, pi)
@ -78,7 +80,7 @@ function customerHistory (customerId, thresholdDays) {
AND fiat > 0
UNION
SELECT txOut.id, txOut.created, txOut.fiat, 'cashOut' AS direction,
(extract(epoch FROM (now() - greatest(txOut.created, txOut.confirmed_at))) * 1000) >= $4 AS expired
(NOT txOut.dispense AND extract(epoch FROM (now() - greatest(txOut.created, txOut.confirmed_at))) >= $4) AS expired
FROM cash_out_txs txOut
WHERE txOut.customer_id = $1
AND txOut.created > now() - interval $2

View file

@ -121,7 +121,7 @@ function mergeStatusMode (a, b) {
}
function getWalletStatus (settings, tx) {
const fudgeFactorEnabled = configManager.getWalletSettings(tx.cryptoCode, settings.config).fudgeFactorActive
const fudgeFactorEnabled = configManager.getGlobalCashOut(settings.config).fudgeFactorActive
const fudgeFactor = fudgeFactorEnabled ? 100 : 0
const requested = tx.cryptoAtoms.minus(fudgeFactor)

View file

@ -7,7 +7,11 @@ exports.up = function (next) {
'cash-out-1-refill',
'cash-out-1-empty',
'cash-out-2-refill',
'cash-out-2-empty'
'cash-out-2-empty',
'cash-out-3-refill',
'cash-out-3-empty',
'cash-out-4-refill',
'cash-out-4-empty'
)`,
`ALTER TABLE cashbox_batches ADD COLUMN operation_type cashbox_batch_type NOT NULL`,
`ALTER TABLE cashbox_batches ADD COLUMN bill_count_override SMALLINT`,

View file

@ -7,6 +7,8 @@ exports.up = function (next) {
.then(({ config }) => {
const fiatBalance1 = config.notifications_fiatBalanceCassette1
const fiatBalance2 = config.notifications_fiatBalanceCassette2
const fiatBalance3 = config.notifications_fiatBalanceCassette3
const fiatBalance4 = config.notifications_fiatBalanceCassette4
const overrides = config.notifications_fiatBalanceOverrides
const newConfig = {}
if (fiatBalance1) {
@ -17,6 +19,14 @@ exports.up = function (next) {
newConfig.notifications_fillingPercentageCassette2 = (100 * (fiatBalance2 / cassetteMaxCapacity)).toFixed(0)
newConfig.notifications_fiatBalanceCassette2 = null
}
if (fiatBalance3) {
newConfig.notifications_fillingPercentageCassette3 = (100 * (fiatBalance3 / cassetteMaxCapacity)).toFixed(0)
newConfig.notifications_fiatBalanceCassette3 = null
}
if (fiatBalance4) {
newConfig.notifications_fillingPercentageCassette4 = (100 * (fiatBalance4 / cassetteMaxCapacity)).toFixed(0)
newConfig.notifications_fiatBalanceCassette4 = null
}
if (overrides) {
newConfig.notifications_fiatBalanceOverrides = _.map(override => {
@ -27,6 +37,12 @@ exports.up = function (next) {
if (override.fiatBalanceCassette2) {
newOverride.fillingPercentageCassette2 = (100 * (override.fiatBalanceCassette2 / cassetteMaxCapacity)).toFixed(0)
}
if (override.fiatBalanceCassette3) {
newOverride.fillingPercentageCassette3 = (100 * (override.fiatBalanceCassette3 / cassetteMaxCapacity)).toFixed(0)
}
if (override.fiatBalanceCassette4) {
newOverride.fillingPercentageCassette4 = (100 * (override.fiatBalanceCassette4 / cassetteMaxCapacity)).toFixed(0)
}
newOverride.machine = override.machine
newOverride.id = override.id

View file

@ -0,0 +1,48 @@
var db = require('./db')
const _ = require('lodash/fp')
const { saveConfig, loadLatest } = require('../lib/new-settings-loader')
const { getMachines } = require('../lib/machine-loader')
exports.up = function (next) {
var sql = [
'ALTER TABLE devices ADD COLUMN cassette3 INTEGER NOT NULL DEFAULT 0',
'ALTER TABLE devices ADD COLUMN cassette4 INTEGER NOT NULL DEFAULT 0',
'ALTER TABLE cash_out_txs ADD COLUMN provisioned_3 INTEGER',
'ALTER TABLE cash_out_txs ADD COLUMN provisioned_4 INTEGER',
'ALTER TABLE cash_out_txs ADD COLUMN denomination_3 INTEGER',
'ALTER TABLE cash_out_txs ADD COLUMN denomination_4 INTEGER',
'ALTER TABLE cash_out_actions ADD COLUMN provisioned_3 INTEGER',
'ALTER TABLE cash_out_actions ADD COLUMN provisioned_4 INTEGER',
'ALTER TABLE cash_out_actions ADD COLUMN dispensed_3 INTEGER',
'ALTER TABLE cash_out_actions ADD COLUMN dispensed_4 INTEGER',
'ALTER TABLE cash_out_actions ADD COLUMN rejected_3 INTEGER',
'ALTER TABLE cash_out_actions ADD COLUMN rejected_4 INTEGER',
'ALTER TABLE cash_out_actions ADD COLUMN denomination_3 INTEGER',
'ALTER TABLE cash_out_actions ADD COLUMN denomination_4 INTEGER',
'ALTER TABLE devices ADD COLUMN number_of_cassettes INTEGER NOT NULL DEFAULT 2'
]
return Promise.all([loadLatest(), getMachines()])
.then(([config, machines]) => {
const formattedMachines = _.map(it => _.pick(['deviceId'], it), machines)
const newConfig = _.reduce((acc, value) => {
if(_.includes(`cashOut_${value.deviceId}_top`, _.keys(config.config))) {
acc[`cashOut_${value.deviceId}_cassette1`] = config.config[`cashOut_${value.deviceId}_top`]
}
if(_.includes(`cashOut_${value.deviceId}_bottom`, _.keys(config.config))) {
acc[`cashOut_${value.deviceId}_cassette2`] = config.config[`cashOut_${value.deviceId}_bottom`]
}
return acc
}, {}, formattedMachines)
return saveConfig(newConfig)
.then(() => db.multi(sql, next))
.catch(err => next(err))
})
}
exports.down = function (next) {
next()
}

View file

@ -34,6 +34,7 @@ const Header = () => {
const {
elements,
enableEdit,
enableEditText,
editWidth,
enableDelete,
deleteWidth,
@ -72,7 +73,7 @@ const Header = () => {
{innerElements.map(mapElement2)}
{enableEdit && (
<Td header width={editWidth} textAlign="center">
Edit
{enableEditText ?? `Edit`}
</Td>
)}
{enableDelete && (

View file

@ -131,6 +131,7 @@ const ECol = ({ editing, focus, config, extraPaddingRight, extraPadding }) => {
suffix,
SuffixComponent = Label2,
textStyle = it => {},
isHidden = it => false,
view = it => it?.toString(),
inputProps = {}
} = config
@ -168,22 +169,25 @@ const ECol = ({ editing, focus, config, extraPaddingRight, extraPadding }) => {
size={size}
bold={bold}
textAlign={textAlign}>
{isEditing && isField && (
{isEditing && isField && !isHidden(values) && (
<Field name={name} component={input} {...innerProps} />
)}
{isEditing && !isField && <config.input name={name} />}
{!isEditing && values && (
{isEditing && !isField && !isHidden(values) && (
<config.input name={name} />
)}
{!isEditing && values && !isHidden(values) && (
<div style={textStyle(values, isEditing)}>
{view(values[name], values)}
</div>
)}
{suffix && (
{suffix && !isHidden(values) && (
<SuffixComponent
className={classes.suffix}
style={isEditing ? {} : textStyle(values, isEditing)}>
{suffix}
</SuffixComponent>
)}
{isHidden(values) && <StripesSvg />}
</Td>
)
}

View file

@ -15,7 +15,7 @@ export default {
paddingRight: 39
},
withSuffix: ({ textAlign }) => {
const justifyContent = textAlign === 'right' ? 'end' : textAlign
const justifyContent = textAlign === 'right' ? 'flex-end' : textAlign
return {
display: 'flex',
alignItems: 'center',

View file

@ -37,6 +37,7 @@ const ETable = ({
validationSchema,
enableCreate,
enableEdit,
enableEditText,
editWidth: outerEditWidth,
enableDelete,
deleteWidth = ACTION_COL_SIZE,
@ -87,7 +88,6 @@ const ETable = ({
}
setAdding(false)
setEditingId(null)
setEditing && setEditing(false)
setSaving(false)
}
@ -139,6 +139,7 @@ const ETable = ({
const ctxValue = {
elements,
enableEdit,
enableEditText,
onEdit,
clearError: () => setError(null),
error: error,

View file

@ -13,6 +13,7 @@ const gridClasses = makeStyles(gridStyles)
const Cashbox = ({
percent = 0,
cashOut = false,
width,
className,
emptyPartClassName,
labelClassName,
@ -21,7 +22,13 @@ const Cashbox = ({
omitInnerPercentage,
isLow
}) => {
const classes = cashboxClasses({ percent, cashOut, applyColorVariant, isLow })
const classes = cashboxClasses({
percent,
cashOut,
width,
applyColorVariant,
isLow
})
const ltHalf = percent <= 51
const showCashBox = {
@ -76,7 +83,8 @@ const CashOut = ({
notes,
className,
editingMode = false,
threshold
threshold,
width
}) => {
const percent = (100 * notes) / capacity
const isLow = percent < threshold
@ -90,6 +98,7 @@ const CashOut = ({
percent={percent}
cashOut
isLow={isLow}
width={width}
/>
</div>
{!editingMode && (

View file

@ -22,7 +22,7 @@ const cashboxStyles = {
borderColor: colorPicker,
backgroundColor: colorPicker,
height: 118,
width: 80,
width: ({ width }) => width ?? 80,
border: '2px solid',
textAlign: 'end',
display: 'inline-block'
@ -65,7 +65,7 @@ const gridStyles = {
justifyContent: 'flex-start'
},
col2: {
marginLeft: 16
marginLeft: 14
},
noMarginText: {
marginTop: 0,

View file

@ -1,4 +1,5 @@
import { makeStyles } from '@material-ui/core'
import classNames from 'classnames'
import React, { memo, useState } from 'react'
import { CashOut } from 'src/components/inputs/cashbox/Cashbox'
@ -9,40 +10,42 @@ const useStyles = makeStyles({
display: 'flex'
},
cashCassette: {
width: 80,
height: 36,
marginRight: 16
marginRight: 14
}
})
const CashCassetteInput = memo(({ decimalPlaces, threshold, ...props }) => {
const classes = useStyles()
const { name, onChange, onBlur, value } = props.field
const { touched, errors } = props.form
const [notes, setNotes] = useState(value)
const error = !!(touched[name] && errors[name])
return (
<div className={classes.flex}>
<CashOut
className={classes.cashCassette}
notes={notes}
editingMode={true}
threshold={threshold}
/>
<NumberInput
name={name}
onChange={e => {
setNotes(e.target.value)
return onChange(e)
}}
onBlur={onBlur}
value={value}
error={error}
decimalPlaces={decimalPlaces}
{...props}
/>
</div>
)
})
const CashCassetteInput = memo(
({ decimalPlaces, width, threshold, inputClassName, ...props }) => {
const classes = useStyles()
const { name, onChange, onBlur, value } = props.field
const { touched, errors } = props.form
const [notes, setNotes] = useState(value)
const error = !!(touched[name] && errors[name])
return (
<div className={classes.flex}>
<CashOut
className={classNames(classes.cashCassette, inputClassName)}
notes={notes}
editingMode={true}
width={width}
threshold={threshold}
/>
<NumberInput
name={name}
onChange={e => {
setNotes(e.target.value)
return onChange(e)
}}
onBlur={onBlur}
value={value}
error={error}
decimalPlaces={decimalPlaces}
{...props}
/>
</div>
)
}
)
export default CashCassetteInput

View file

@ -18,8 +18,7 @@ import { DenominationsSchema, getElements } from './helper'
const useStyles = makeStyles({
fudgeFactor: {
display: 'flex',
alignItems: 'center',
marginRight: 156
alignItems: 'center'
},
switchLabel: {
margin: 6,
@ -44,6 +43,9 @@ const GET_INFO = gql`
cashbox
cassette1
cassette2
cassette3
cassette4
numberOfCassettes
}
config
}
@ -65,6 +67,7 @@ const CashOut = ({ name: SCREEN_KEY }) => {
}
const config = data?.config && fromNamespace(SCREEN_KEY)(data.config)
const fudgeFactorActive = config?.fudgeFactorActive ?? false
const locale = data?.config && fromNamespace('locale')(data.config)
const machines = data?.machines ?? []

View file

@ -7,16 +7,17 @@ import { Autocomplete } from 'src/components/inputs/formik'
import denominations from 'src/utils/bill-denominations'
import { getBillOptions } from 'src/utils/bill-options'
import { toNamespace } from 'src/utils/config'
import { transformNumber } from 'src/utils/number'
import WizardSplash from './WizardSplash'
import WizardStep from './WizardStep'
import { DenominationsSchema } from './helper'
const LAST_STEP = 3
const MODAL_WIDTH = 554
const MODAL_HEIGHT = 520
const Wizard = ({ machine, locale, onClose, save, error }) => {
const LAST_STEP = machine.numberOfCassettes + 2
const [{ step, config }, setState] = useState({
step: 0,
config: { active: true }
@ -30,7 +31,10 @@ const Wizard = ({ machine, locale, onClose, save, error }) => {
const onContinue = async it => {
if (isLastStep) {
return save(
toNamespace(machine.deviceId, DenominationsSchema.cast(config))
toNamespace(
machine.deviceId,
DenominationsSchema.cast(config, { assert: false })
)
)
}
@ -42,33 +46,55 @@ const Wizard = ({ machine, locale, onClose, save, error }) => {
})
}
const steps = [
{
type: 'top',
display: 'Cassette 1 (Top)',
component: Autocomplete,
inputProps: {
options: R.map(it => ({ code: it, display: it }))(options),
labelProp: 'display',
valueProp: 'code'
}
const steps = []
R.until(
R.gt(R.__, machine.numberOfCassettes),
it => {
steps.push({
type: `cassette${it}`,
display: `Cassette ${it}`,
component: Autocomplete,
inputProps: {
options: R.map(it => ({ code: it, display: it }))(options),
labelProp: 'display',
valueProp: 'code'
}
})
return R.add(1, it)
},
{
type: 'bottom',
display: 'Cassette 2',
component: Autocomplete,
inputProps: {
options: R.map(it => ({ code: it, display: it }))(options),
labelProp: 'display',
valueProp: 'code'
}
}
]
1
)
steps.push({
type: 'zeroConfLimit',
display: '0-conf Limit',
schema: Yup.object().shape({
zeroConfLimit: Yup.number().required()
})
})
const schema = () =>
Yup.object().shape({
top: Yup.number().required(),
bottom: step >= 2 ? Yup.number().required() : Yup.number()
cassette1: Yup.number().required(),
cassette2:
machine.numberOfCassettes > 1 && step >= 2
? Yup.number().required()
: Yup.number()
.transform(transformNumber)
.nullable(),
cassette3:
machine.numberOfCassettes > 2 && step >= 3
? Yup.number().required()
: Yup.number()
.transform(transformNumber)
.nullable(),
cassette4:
machine.numberOfCassettes > 3 && step >= 4
? Yup.number().required()
: Yup.number()
.transform(transformNumber)
.nullable()
})
return (
@ -85,6 +111,7 @@ const Wizard = ({ machine, locale, onClose, save, error }) => {
<WizardStep
step={step}
name={machine.name}
numberOfCassettes={machine.numberOfCassettes}
error={error}
lastStep={isLastStep}
steps={steps}

View file

@ -9,11 +9,36 @@ import { NumberInput } from 'src/components/inputs/formik'
import { Info2, H4, P, Info1, Label1 } from 'src/components/typography'
import cassetteOne from 'src/styling/icons/cassettes/cashout-cassette-1.svg'
import cassetteTwo from 'src/styling/icons/cassettes/cashout-cassette-2.svg'
import tejo3CassetteOne from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-1-left.svg'
import tejo3CassetteTwo from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-2-left.svg'
import tejo3CassetteThree from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-3-left.svg'
import tejo4CassetteOne from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-1-left.svg'
import tejo4CassetteTwo from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-2-left.svg'
import tejo4CassetteThree from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-3-left.svg'
import tejo4CassetteFour from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-4-left.svg'
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg'
import styles from './WizardStep.styles'
const useStyles = makeStyles(styles)
const getCassetesArtworks = () => ({
2: {
1: cassetteOne,
2: cassetteTwo
},
3: {
1: tejo3CassetteOne,
2: tejo3CassetteTwo,
3: tejo3CassetteThree
},
4: {
1: tejo4CassetteOne,
2: tejo4CassetteTwo,
3: tejo4CassetteThree,
4: tejo4CassetteFour
}
})
const WizardStep = ({
name,
step,
@ -23,30 +48,31 @@ const WizardStep = ({
onContinue,
steps,
fiatCurrency,
options
options,
numberOfCassettes
}) => {
const classes = useStyles()
const label = lastStep ? 'Finish' : 'Next'
const cassetesArtworks = {
1: cassetteOne,
2: cassetteTwo
}
return (
<div className={classes.content}>
<>
<div className={classes.titleDiv}>
<Info2 className={classes.title}>{name}</Info2>
<Stepper steps={3} currentStep={step} />
<Stepper steps={steps.length + 1} currentStep={step} />
</div>
{step <= 2 && (
{step <= numberOfCassettes && (
<Formik
validateOnBlur={false}
validateOnChange={false}
onSubmit={onContinue}
initialValues={{ top: '', bottom: '' }}
initialValues={{
cassette1: '',
cassette2: '',
cassette3: '',
cassette4: ''
}}
enableReinitialize
validationSchema={schema}>
<Form>
@ -84,8 +110,8 @@ const WizardStep = ({
className={classes.stepImage}
alt="cassette"
width="148"
height="196"
src={cassetesArtworks[step]}></img>
height="205"
src={getCassetesArtworks()[numberOfCassettes][step]}></img>
</div>
<Button className={classes.submit} type="submit">
@ -94,6 +120,46 @@ const WizardStep = ({
</Form>
</Formik>
)}
{step === numberOfCassettes + 1 && (
<Formik
validateOnBlur={false}
validateOnChange={false}
onSubmit={onContinue}
initialValues={{ zeroConfLimit: '' }}
enableReinitialize
validationSchema={steps[step - 1].schema}>
<Form>
<div className={classes.thirdStepHeader}>
<div className={classes.step}>
<H4 className={classes.edit}>Edit 0-conf Limit</H4>
<Label1>Choose a limit</Label1>
<div className={classes.bill}>
<Field
className={classes.billInput}
type="text"
size="lg"
autoFocus={true}
component={NumberInput}
fullWidth
decimalPlaces={0}
name={steps[step - 1].type}
/>
<Info1 noMargin className={classes.suffix}>
{fiatCurrency}
</Info1>
</div>
</div>
</div>
<Button className={classes.submit} type="submit">
{label}
</Button>
</Form>
</Formik>
)}
{lastStep && (
<div className={classes.disclaimer}>
<Info2 className={classes.title}>Cash-out Bill Count</Info2>
@ -122,7 +188,7 @@ const WizardStep = ({
</div>
</div>
)}
</div>
</>
)
}

View file

@ -41,7 +41,7 @@ export default {
},
header: {
display: 'flex',
paddingBottom: 95
marginBottom: 95
},
thirdStepHeader: {
display: 'flex',

View file

@ -6,21 +6,44 @@ import { bold } from 'src/styling/helpers'
import denominations from 'src/utils/bill-denominations'
import { getBillOptions } from 'src/utils/bill-options'
import { CURRENCY_MAX } from 'src/utils/constants'
import { transformNumber } from 'src/utils/number'
const DenominationsSchema = Yup.object().shape({
top: Yup.number()
.label('Cassette 1 (Top)')
cassette1: Yup.number()
.label('Cassette 1')
.required()
.min(1)
.max(CURRENCY_MAX),
bottom: Yup.number()
.label('Cassette 2 (Bottom)')
cassette2: Yup.number()
.label('Cassette 2')
.required()
.min(1)
.max(CURRENCY_MAX),
cassette3: Yup.number()
.label('Cassette 3')
.min(1)
.max(CURRENCY_MAX)
.nullable()
.transform(transformNumber),
cassette4: Yup.number()
.label('Cassette 4')
.min(1)
.max(CURRENCY_MAX)
.nullable()
.transform(transformNumber),
zeroConfLimit: Yup.number()
.label('0-conf Limit')
.required()
.min(0)
.max(CURRENCY_MAX)
})
const getElements = (machines, locale = {}, classes) => {
const fiatCurrency = R.prop('fiatCurrency')(locale)
const maxNumberOfCassettes = Math.max(
...R.map(it => it.numberOfCassettes, machines)
)
const options = getBillOptions(locale, denominations)
const cassetteProps =
options?.length > 0
@ -32,7 +55,7 @@ const getElements = (machines, locale = {}, classes) => {
}
: { decimalPlaces: 0 }
return [
const elements = [
{
name: 'id',
header: 'Machine',
@ -40,32 +63,50 @@ const getElements = (machines, locale = {}, classes) => {
view: it => machines.find(({ deviceId }) => deviceId === it).name,
size: 'sm',
editable: false
},
{
name: 'top',
header: 'Cassette 1 (Top)',
stripe: true,
width: 250,
textAlign: 'right',
view: it => it,
input: options?.length > 0 ? Autocomplete : NumberInput,
inputProps: cassetteProps,
suffix: R.prop('fiatCurrency')(locale),
bold: bold
},
{
name: 'bottom',
header: 'Cassette 2 (Bottom)',
stripe: true,
textAlign: 'right',
width: 250,
view: it => it,
input: options?.length > 0 ? Autocomplete : NumberInput,
inputProps: cassetteProps,
suffix: R.prop('fiatCurrency')(locale),
bold: bold
}
]
R.until(
R.gt(R.__, maxNumberOfCassettes),
it => {
elements.push({
name: `cassette${it}`,
header: `Cassette ${it}`,
size: 'sm',
stripe: true,
textAlign: 'right',
width: (maxNumberOfCassettes > 2 ? 600 : 460) / maxNumberOfCassettes,
suffix: fiatCurrency,
bold: bold,
view: it => it,
input: options?.length > 0 ? Autocomplete : NumberInput,
inputProps: cassetteProps,
doubleHeader: 'Denominations',
isHidden: machine =>
it >
machines.find(({ deviceId }) => deviceId === machine.id)
.numberOfCassettes
})
return R.add(1, it)
},
1
)
elements.push({
name: 'zeroConfLimit',
header: '0-conf Limit',
size: 'sm',
stripe: true,
textAlign: 'right',
width: maxNumberOfCassettes > 2 ? 150 : 290,
input: NumberInput,
inputProps: {
decimalPlaces: 0
},
suffix: fiatCurrency
})
return elements
}
export { DenominationsSchema, getElements }

View file

@ -6,6 +6,7 @@ import TableContainer from '@material-ui/core/TableContainer'
import TableHead from '@material-ui/core/TableHead'
import TableRow from '@material-ui/core/TableRow'
import classnames from 'classnames'
import * as R from 'ramda'
import React from 'react'
import { useHistory } from 'react-router-dom'
@ -60,6 +61,10 @@ const MachinesTable = ({ machines, numToRender }) => {
})
}
const maxNumberOfCassettes = Math.max(
...R.map(it => it.numberOfCassettes, machines)
)
return (
<TableContainer className={classes.table}>
<Table>
@ -80,18 +85,17 @@ const MachinesTable = ({ machines, numToRender }) => {
<TxInIcon />
</div>
</HeaderCell> */}
<HeaderCell>
<div className={classes.header}>
<TxOutIcon />
<Label2 className={classes.label}> 1</Label2>
</div>
</HeaderCell>
<HeaderCell>
<div className={classes.header}>
<TxOutIcon />
<Label2 className={classes.label}> 2</Label2>
</div>
</HeaderCell>
{R.map(
it => (
<HeaderCell>
<div className={classes.header}>
<TxOutIcon />
<Label2 className={classes.label}> {it + 1}</Label2>
</div>
</HeaderCell>
),
R.times(R.identity, maxNumberOfCassettes)
)}
</TableRow>
</TableHead>
<TableBody>
@ -120,12 +124,19 @@ const MachinesTable = ({ machines, numToRender }) => {
{/* <StyledCell align="left">
{makePercentageText(machine.cashbox)}
</StyledCell> */}
<StyledCell align="left">
{makePercentageText(machine.cassette1)}
</StyledCell>
<StyledCell align="left">
{makePercentageText(machine.cassette2)}
</StyledCell>
{R.map(
it =>
machine.numberOfCassettes > it ? (
<StyledCell align="left">
{makePercentageText(machine[`cassette${it + 1}`])}
</StyledCell>
) : (
<StyledCell align="left">
<TL2>{`— %`}</TL2>
</StyledCell>
),
R.times(R.identity, maxNumberOfCassettes)
)}
</TableRow>
)
}

View file

@ -27,6 +27,9 @@ const GET_DATA = gql`
cashbox
cassette1
cassette2
cassette3
cassette4
numberOfCassettes
statuses {
label
type

View file

@ -1,18 +1,25 @@
import { useMutation } from '@apollo/react-hooks'
import { makeStyles } from '@material-ui/core'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React from 'react'
import * as Yup from 'yup'
import { Table as EditableTable } from 'src/components/editableTable'
import { CashOut, CashIn } from 'src/components/inputs/cashbox/Cashbox'
import { NumberInput } from 'src/components/inputs/formik'
import { NumberInput, CashCassetteInput } from 'src/components/inputs/formik'
import { fromNamespace } from 'src/utils/config'
import styles from './Cassettes.styles'
const useStyles = makeStyles(styles)
const widthsByNumberOfCassettes = {
2: { cashbox: 116, cassette: 280, cassetteGraph: 80, editWidth: 174 },
3: { cashbox: 106, cassette: 200, cassetteGraph: 60, editWidth: 145 },
4: { cashbox: 106, cassette: 164, cassetteGraph: 40, editWidth: 90 }
}
const ValidationSchema = Yup.object().shape({
name: Yup.string().required('Required'),
cashbox: Yup.number()
@ -27,6 +34,16 @@ const ValidationSchema = Yup.object().shape({
.min(0)
.max(500),
cassette2: Yup.number()
.required('Required')
.integer()
.min(0)
.max(500),
cassette3: Yup.number()
.required('Required')
.integer()
.min(0)
.max(500),
cassette4: Yup.number()
.required('Required')
.integer()
.min(0)
@ -40,6 +57,8 @@ const SET_CASSETTE_BILLS = gql`
$cashbox: Int!
$cassette1: Int!
$cassette2: Int!
$cassette3: Int!
$cassette4: Int!
) {
machineAction(
deviceId: $deviceId
@ -47,11 +66,15 @@ const SET_CASSETTE_BILLS = gql`
cashbox: $cashbox
cassette1: $cassette1
cassette2: $cassette2
cassette3: $cassette3
cassette4: $cassette4
) {
deviceId
cashbox
cassette1
cassette2
cassette3
cassette4
}
}
`
@ -64,6 +87,7 @@ const CashCassettes = ({ machine, config, refetchData }) => {
const fillingPercentageSettings =
config && fromNamespace('notifications', config)
const fiatCurrency = locale?.fiatCurrency
const numberOfCassettes = machine.numberOfCassettes
const getCashoutSettings = deviceId => fromNamespace(deviceId)(cashout)
const isCashOutDisabled = ({ deviceId }) =>
@ -73,7 +97,7 @@ const CashCassettes = ({ machine, config, refetchData }) => {
{
name: 'cashbox',
header: 'Cashbox',
width: 240,
width: widthsByNumberOfCassettes[numberOfCassettes].cashbox,
stripe: false,
view: value => (
<CashIn currency={{ code: fiatCurrency }} notes={value} total={0} />
@ -82,61 +106,63 @@ const CashCassettes = ({ machine, config, refetchData }) => {
inputProps: {
decimalPlaces: 0
}
},
{
name: 'cassette1',
header: 'Cash-out 1',
width: 265,
stripe: true,
view: (value, { deviceId }) => (
<CashOut
className={classes.cashbox}
denomination={getCashoutSettings(deviceId)?.top}
currency={{ code: fiatCurrency }}
notes={value}
threshold={fillingPercentageSettings.fillingPercentageCassette1}
/>
),
input: NumberInput,
inputProps: {
decimalPlaces: 0
}
},
{
name: 'cassette2',
header: 'Cash-out 2',
width: 265,
stripe: true,
view: (value, { deviceId }) => {
return (
<CashOut
className={classes.cashbox}
denomination={getCashoutSettings(deviceId)?.bottom}
currency={{ code: fiatCurrency }}
notes={value}
threshold={fillingPercentageSettings.fillingPercentageCassette2}
/>
)
},
input: NumberInput,
inputProps: {
decimalPlaces: 0
}
}
]
R.until(
R.gt(R.__, numberOfCassettes),
it => {
elements.push({
name: `cassette${it}`,
header: `Cash-out ${it}`,
width: widthsByNumberOfCassettes[numberOfCassettes].cassette,
stripe: true,
doubleHeader: 'Cash-out',
view: value => {
return (
<CashOut
className={classes.cashbox}
denomination={
getCashoutSettings(machine.deviceId)?.[`cassette${it}`]
}
currency={{ code: fiatCurrency }}
notes={value}
width={widthsByNumberOfCassettes[numberOfCassettes].cassetteGraph}
threshold={
fillingPercentageSettings[`fillingPercentageCassette${it}`]
}
/>
)
},
isHidden: ({ numberOfCassettes }) => it > numberOfCassettes,
input: CashCassetteInput,
inputProps: {
decimalPlaces: 0,
width: widthsByNumberOfCassettes[numberOfCassettes].cassetteGraph,
inputClassName: classes.cashbox
}
})
return R.add(1, it)
},
1
)
const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, {
refetchQueries: () => refetchData()
})
const onSave = (...[, { deviceId, cashbox, cassette1, cassette2 }]) => {
const onSave = (
...[, { deviceId, cashbox, cassette1, cassette2, cassette3, cassette4 }]
) => {
return setCassetteBills({
variables: {
action: 'setCassetteBills',
deviceId: deviceId,
cashbox,
cassette1,
cassette2
cassette2,
cassette3,
cassette4
}
})
}
@ -144,6 +170,8 @@ const CashCassettes = ({ machine, config, refetchData }) => {
return machine.name ? (
<EditableTable
error={error?.message}
enableEdit
editWidth={widthsByNumberOfCassettes[numberOfCassettes].editWidth}
stripeWhen={isCashOutDisabled}
disableRowEdit={isCashOutDisabled}
name="cashboxes"

View file

@ -1,6 +1,5 @@
const styles = {
cashbox: {
width: 80,
height: 36
}
}

View file

@ -32,6 +32,9 @@ const GET_INFO = gql`
cashbox
cassette1
cassette2
cassette3
cassette4
numberOfCassettes
statuses {
label
type
@ -84,15 +87,6 @@ const Machines = () => {
<Overview data={machine} onActionSuccess={refetch} />
</div>
</Grid>
<Grid item xs={12}>
{/* on hold for now <Sidebar
isSelected={R.equals(selectedMachine)}
selectItem={setSelectedMachine}
data={machines}
getText={R.prop('name')}
getKey={R.prop('deviceId')}
/> */}
</Grid>
</Grid>
<Grid item xs={9}>
<div className={classes.content}>

View file

@ -37,19 +37,39 @@ const ValidationSchema = Yup.object().shape({
.min(0)
.max(1000),
cassette1: Yup.number()
.label('Cassette 1 (top)')
.label('Cassette 1')
.required()
.integer()
.min(0)
.max(500),
cassette2: Yup.number()
.label('Cassette 2 (bottom)')
.label('Cassette 2')
.required()
.integer()
.min(0)
.max(500),
cassette3: Yup.number()
.label('Cassette 3')
.required()
.integer()
.min(0)
.max(500),
cassette4: Yup.number()
.label('Cassette 4')
.required()
.integer()
.min(0)
.max(500)
})
const CREATE_BATCH = gql`
mutation createBatch($deviceId: ID, $cashboxCount: Int) {
createBatch(deviceId: $deviceId, cashboxCount: $cashboxCount) {
id
}
}
`
const GET_MACHINES_AND_CONFIG = gql`
query getData {
machines {
@ -58,6 +78,9 @@ const GET_MACHINES_AND_CONFIG = gql`
cashbox
cassette1
cassette2
cassette3
cassette4
numberOfCassettes
}
config
}
@ -85,6 +108,8 @@ const SET_CASSETTE_BILLS = gql`
$cashbox: Int!
$cassette1: Int!
$cassette2: Int!
$cassette3: Int!
$cassette4: Int!
) {
machineAction(
deviceId: $deviceId
@ -92,11 +117,15 @@ const SET_CASSETTE_BILLS = gql`
cashbox: $cashbox
cassette1: $cassette1
cassette2: $cassette2
cassette3: $cassette3
cassette4: $cassette4
) {
deviceId
cashbox
cassette1
cassette2
cassette3
cassette4
}
}
`
@ -117,6 +146,7 @@ const CashCassettes = () => {
const [setCassetteBills, { error }] = useMutation(SET_CASSETTE_BILLS, {
refetchQueries: () => ['getData']
})
const [createBatch] = useMutation(CREATE_BATCH)
const [saveConfig] = useMutation(SAVE_CONFIG, {
onCompleted: () => setEditingSchema(false),
refetchQueries: () => ['getData']
@ -129,6 +159,37 @@ const CashCassettes = () => {
const cashout = data?.config && fromNamespace('cashOut')(data.config)
const locale = data?.config && fromNamespace('locale')(data.config)
const fiatCurrency = locale?.fiatCurrency
const maxNumberOfCassettes = Math.max(
...R.map(it => it.numberOfCassettes, machines)
)
const cashboxCounts = R.reduce(
(ret, m) => R.assoc(m.id, m.cashbox, ret),
{},
machines
)
const onSave = (id, cashbox, cassette1, cassette2, cassette3, cassette4) => {
const oldCashboxCount = cashboxCounts[id]
if (cashbox < oldCashboxCount) {
createBatch({
variables: {
deviceId: id,
cashboxCount: oldCashboxCount
}
})
}
return setCassetteBills({
variables: {
action: 'setCassetteBills',
deviceId: id,
cashbox,
cassette1,
cassette2,
cassette3,
cassette4
}
})
}
const cashboxReset =
data?.config && fromNamespace('cashIn')(data.config).cashboxReset
@ -144,17 +205,7 @@ const CashCassettes = () => {
setEditingSchema(false)
}
}
const onSave = (id, cashbox, cassette1, cassette2) => {
return setCassetteBills({
variables: {
action: 'setCassetteBills',
deviceId: id,
cashbox,
cassette1,
cassette2
}
})
}
const getCashoutSettings = id => fromNamespace(id)(cashout)
const isCashOutDisabled = ({ id }) => !getCashoutSettings(id).active
@ -172,14 +223,14 @@ const CashCassettes = () => {
{
name: 'name',
header: 'Machine',
width: 254,
width: 184,
view: name => <>{name}</>,
input: ({ field: { value: name } }) => <>{name}</>
},
{
name: 'cashbox',
header: 'Cashbox',
width: 240,
header: 'Cash-in',
width: maxNumberOfCassettes > 2 ? 140 : 280,
view: value => (
<CashIn currency={{ code: fiatCurrency }} notes={value} total={0} />
),
@ -187,68 +238,60 @@ const CashCassettes = () => {
inputProps: {
decimalPlaces: 0
}
},
{
name: 'cassette1',
header: 'Cassette 1 (Top)',
width: 265,
stripe: true,
view: (value, { id }) => (
<CashOut
className={classes.cashbox}
denomination={getCashoutSettings(id)?.top}
currency={{ code: fiatCurrency }}
notes={value}
threshold={fillingPercentageSettings.fillingPercentageCassette1}
/>
),
input: CashCassetteInput,
inputProps: {
decimalPlaces: 0,
threshold: fillingPercentageSettings.fillingPercentageCassette1
}
},
{
name: 'cassette2',
header: 'Cassette 2 (Bottom)',
width: 265,
stripe: true,
view: (value, { id }) => {
return (
<CashOut
className={classes.cashbox}
denomination={getCashoutSettings(id)?.bottom}
currency={{ code: fiatCurrency }}
notes={value}
threshold={fillingPercentageSettings.fillingPercentageCassette2}
/>
)
},
input: CashCassetteInput,
inputProps: {
decimalPlaces: 0,
threshold: fillingPercentageSettings.fillingPercentageCassette2
}
},
{
name: 'edit',
header: 'Edit',
width: 175,
textAlign: 'center',
view: (value, { id }) => {
return (
<IconButton
onClick={() => {
setMachineId(id)
setWizard(true)
}}>
<EditIcon />
</IconButton>
)
}
}
]
R.until(
R.gt(R.__, maxNumberOfCassettes),
it => {
elements.push({
name: `cassette${it}`,
header: `Cassette ${it}`,
width: (maxNumberOfCassettes > 2 ? 700 : 560) / maxNumberOfCassettes,
stripe: true,
doubleHeader: 'Cash-out',
view: (value, { id }) => (
<CashOut
className={classes.cashbox}
denomination={getCashoutSettings(id)?.[`cassette${it}`]}
currency={{ code: fiatCurrency }}
notes={value}
width={50}
threshold={
fillingPercentageSettings[`fillingPercentageCassette${it}`]
}
/>
),
isHidden: ({ numberOfCassettes }) => it > numberOfCassettes,
input: CashCassetteInput,
inputProps: {
decimalPlaces: 0,
width: 50,
inputClassName: classes.cashbox
}
})
return R.add(1, it)
},
1
)
elements.push({
name: 'edit',
header: 'Edit',
width: 87,
view: (value, { id }) => {
return (
<IconButton
onClick={() => {
setMachineId(id)
setWizard(true)
}}>
<EditIcon />
</IconButton>
)
}
})
return (
<>
<TitleSection
@ -292,7 +335,6 @@ const CashCassettes = () => {
stripeWhen={isCashOutDisabled}
elements={elements}
data={machines}
save={onSave}
validationSchema={ValidationSchema}
tbodyWrapperClass={classes.tBody}
/>

View file

@ -2,7 +2,6 @@ import { offColor } from 'src/styling/variables'
export default {
cashbox: {
width: 80,
height: 36
},
tableContainer: {

View file

@ -25,16 +25,23 @@ const CashCassettesFooter = ({
const classes = useStyles()
const cashout = config && fromNamespace('cashOut')(config)
const getCashoutSettings = id => fromNamespace(id)(cashout)
const reducerFn = (acc, { cassette1, cassette2, id }) => {
const topDenomination = getCashoutSettings(id).top ?? 0
const bottomDenomination = getCashoutSettings(id).bottom ?? 0
const reducerFn = (
acc,
{ cassette1, cassette2, cassette3, cassette4, id }
) => {
const cassette1Denomination = getCashoutSettings(id).cassette1 ?? 0
const cassette2Denomination = getCashoutSettings(id).cassette2 ?? 0
const cassette3Denomination = getCashoutSettings(id).cassette3 ?? 0
const cassette4Denomination = getCashoutSettings(id).cassette4 ?? 0
return [
(acc[0] += cassette1 * topDenomination),
(acc[1] += cassette2 * bottomDenomination)
(acc[0] += cassette1 * cassette1Denomination),
(acc[1] += cassette2 * cassette2Denomination),
(acc[2] += cassette3 * cassette3Denomination),
(acc[3] += cassette4 * cassette4Denomination)
]
}
const totalInCassettes = R.sum(R.reduce(reducerFn, [0, 0], machines))
const totalInCassettes = R.sum(R.reduce(reducerFn, [0, 0, 0, 0], machines))
/* const totalInCashBox = R.sum(
R.flatten(

View file

@ -76,38 +76,34 @@ const CashboxHistory = ({ machines, currency }) => {
const batches = R.path(['cashboxBatches'])(data)
const getOperationRender = {
'cash-in-empty': (
<>
<TxInIcon />
<span className={classes.operationType}>Cash-in emptied</span>
</>
),
'cash-out-1-refill': (
<>
<TxOutIcon />
<span className={classes.operationType}>Cash-out 1 refill</span>
</>
),
'cash-out-1-empty': (
<>
<TxOutIcon />
<span className={classes.operationType}>Cash-out 1 emptied</span>
</>
),
'cash-out-2-refill': (
<>
<TxOutIcon />
<span className={classes.operationType}>Cash-out 2 refill</span>
</>
),
'cash-out-2-empty': (
<>
<TxOutIcon />
<span className={classes.operationType}>Cash-out 2 emptied</span>
</>
)
}
const getOperationRender = R.reduce(
(ret, i) =>
R.pipe(
R.assoc(
`cash-out-${i}-refill`,
<>
<TxOutIcon />
<span className={classes.operationType}>Cash-out {i} refill</span>
</>
),
R.assoc(
`cash-out-${i}-empty`,
<>
<TxOutIcon />
<span className={classes.operationType}>Cash-out {i} emptied</span>
</>
)
)(ret),
{
'cash-in-empty': (
<>
<TxInIcon />
<span className={classes.operationType}>Cash-in emptied</span>
</>
)
},
R.range(1, 5)
)
const save = row => {
const field = R.find(f => f.id === row.id, fields)

View file

@ -32,7 +32,9 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => {
const isCashOutDisabled =
R.isEmpty(cashoutSettings) || !cashoutSettings?.active
const LAST_STEP = isCashOutDisabled ? 1 : 3
const numberOfCassettes = isCashOutDisabled ? 0 : machine.numberOfCassettes
const LAST_STEP = numberOfCassettes + 1
const title = `Update counts`
const isLastStep = step === LAST_STEP
@ -57,12 +59,8 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => {
})
}
save(
machine.id,
parseInt(cashbox),
parseInt(it.cassette1Count ?? 0),
parseInt(it.cassette2Count ?? 0)
)
const { cassette1, cassette2, cassette3, cassette4 } = R.map(parseInt, it)
save(machine.id, cashbox, cassette1, cassette2, cassette3, cassette4)
return onClose()
}
@ -72,7 +70,24 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => {
})
}
const steps = [
const makeCassetteSteps = R.pipe(
R.add(1),
R.range(1),
R.map(i => ({
type: `cassette ${i}`,
schema: Yup.object().shape({
[`cassette${i}`]: Yup.number()
.label('Bill count')
.positive()
.integer()
.required()
.min(0)
.max(CASHBOX_DEFAULT_CAPACITY)
})
}))
)
const steps = R.prepend(
{
type: 'cashbox',
schema: Yup.object().shape({
@ -80,33 +95,8 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => {
}),
cashoutRequired: false
},
{
type: 'cassette 1',
schema: Yup.object().shape({
cassette1Count: Yup.number()
.label('Bill count')
.required()
.min(0)
.max(CASHBOX_DEFAULT_CAPACITY)
}),
cashoutRequired: true
},
{
type: 'cassette 2',
schema: Yup.object().shape({
cassette2Count: Yup.number()
.label('Bill count')
.required()
.min(0)
.max(CASHBOX_DEFAULT_CAPACITY)
}),
cashoutRequired: true
}
]
const filteredSteps = R.filter(it => {
return !it.cashoutRequired || (!isCashOutDisabled && it.cashoutRequired)
}, steps)
makeCassetteSteps(numberOfCassettes)
)
return (
<Modal
@ -127,7 +117,7 @@ const Wizard = ({ machine, cashoutSettings, locale, onClose, save, error }) => {
cassetteCapacity={CASHBOX_DEFAULT_CAPACITY}
error={error}
lastStep={isLastStep}
steps={filteredSteps}
steps={steps}
fiatCurrency={locale.fiatCurrency}
onContinue={onContinue}
/>

View file

@ -13,6 +13,13 @@ import { Info2, H4, P, Info1 } from 'src/components/typography'
import cashbox from 'src/styling/icons/cassettes/acceptor-left.svg'
import cassetteOne from 'src/styling/icons/cassettes/dispenser-1.svg'
import cassetteTwo from 'src/styling/icons/cassettes/dispenser-2.svg'
import tejo3CassetteOne from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-1-left.svg'
import tejo3CassetteTwo from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-2-left.svg'
import tejo3CassetteThree from 'src/styling/icons/cassettes/tejo/3-cassettes/3-cassettes-open-3-left.svg'
import tejo4CassetteOne from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-1-left.svg'
import tejo4CassetteTwo from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-2-left.svg'
import tejo4CassetteThree from 'src/styling/icons/cassettes/tejo/4-cassettes/4-cassettes-open-3-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 { comet, errorColor } from 'src/styling/variables'
@ -91,6 +98,13 @@ const styles = {
const useStyles = makeStyles(styles)
const cassetesArtworks = (numberOfCassettes, step) =>
[
[cassetteOne, cassetteTwo],
[tejo3CassetteOne, tejo3CassetteTwo, tejo3CassetteThree],
[tejo4CassetteOne, tejo4CassetteTwo, tejo4CassetteThree, tejo4CassetteFour]
][numberOfCassettes - 2][step - 2]
const WizardStep = ({
step,
name,
@ -107,28 +121,23 @@ const WizardStep = ({
const label = lastStep ? 'Finish' : 'Confirm'
const cassetesArtworks = {
1: cashbox,
2: cassetteOne,
3: cassetteTwo
}
const stepOneRadioOptions = [
{ display: 'Yes', code: 'YES' },
{ display: 'No', code: 'NO' }
]
const cassetteInfo = {
amount: step === 2 ? machine?.cassette1 : machine?.cassette2,
denomination: step === 2 ? cashoutSettings.top : cashoutSettings.bottom
}
const cassetteField = `cassette${step - 1}`
const numberOfCassettes = machine.numberOfCassettes
const originalCassetteCount = machine?.[cassetteField]
const cassetteDenomination = cashoutSettings?.[cassetteField]
const getPercentage = values => {
const cassetteCount =
step === 2 ? values.cassette1Count : values.cassette2Count
const value = cassetteCount ?? cassetteInfo.amount
return R.clamp(0, 100, 100 * (value / cassetteCapacity))
}
const cassetteCount = values => values[cassetteField] || originalCassetteCount
const cassetteTotal = values => cassetteCount(values) * cassetteDenomination
const getPercentage = R.pipe(
cassetteCount,
count => 100 * (count / cassetteCapacity),
R.clamp(0, 100)
)
return (
<div className={classes.content}>
@ -144,7 +153,7 @@ const WizardStep = ({
onSubmit={onContinue}
initialValues={{ wasCashboxEmptied: '' }}
enableReinitialize
validationSchema={steps[step - 1].schema}>
validationSchema={steps[0].schema}>
{({ values, errors }) => (
<Form>
<div
@ -152,7 +161,7 @@ const WizardStep = ({
<img
className={classes.stepImage}
alt="cassette"
src={cassetesArtworks[step]}></img>
src={cashbox}></img>
<div className={classes.formWrapper}>
<div
className={classnames(
@ -205,12 +214,17 @@ const WizardStep = ({
</Formik>
)}
{(step === 2 || step === 3) && (
{step > 1 && (
<Formik
validateOnBlur={false}
validateOnChange={false}
onSubmit={onContinue}
initialValues={{ cassette1Count: '', cassette2Count: '' }}
initialValues={{
cassette1: '',
cassette2: '',
cassette3: '',
cassette4: ''
}}
enableReinitialize
validationSchema={steps[step - 1].schema}>
{({ values, errors }) => (
@ -220,7 +234,7 @@ const WizardStep = ({
<img
className={classes.stepImage}
alt="cassette"
src={cassetesArtworks[step]}></img>
src={cassetesArtworks(numberOfCassettes, step)}></img>
<div className={classes.formWrapper}>
<div
className={classnames(
@ -260,27 +274,16 @@ const WizardStep = ({
component={NumberInput}
decimalPlaces={0}
width={50}
placeholder={cassetteInfo.amount.toString()}
name={`cassette${step - 1}Count`}
placeholder={originalCassetteCount.toString()}
name={cassetteField}
className={classes.cashboxBills}
/>
<P>
{cassetteInfo.denomination} {fiatCurrency} bills loaded
{cassetteDenomination} {fiatCurrency} bills loaded
</P>
</div>
<P noMargin className={classes.fiatTotal}>
={' '}
{step === 2
? (values.cassette1Count ?? 0) *
cassetteInfo.denomination
: (values.cassette2Count ?? 0) *
cassetteInfo.denomination}{' '}
{fiatCurrency}
</P>
<P className={classes.errorMessage}>
{step === 2
? errors.cassette1Count
: errors.cassette2Count}
= {cassetteTotal(values)} {fiatCurrency}
</P>
</div>
</div>

View file

@ -22,6 +22,7 @@ const GET_INFO = gql`
machines {
name
deviceId
numberOfCassettes
}
cryptoCurrencies {
code

View file

@ -18,13 +18,23 @@ import styles from './FiatBalanceAlerts.styles.js'
const useStyles = makeStyles(styles)
const NAME = 'fiatBalanceAlerts'
const DEFAULT_NUMBER_OF_CASSETTES = 2
const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
const { isEditing, isDisabled, setEditing, data, save } = useContext(
NotificationsCtx
)
const {
isEditing,
isDisabled,
setEditing,
data,
save,
machines
} = useContext(NotificationsCtx)
const classes = useStyles()
const maxNumberOfCassettes =
Math.max(...R.map(it => it.numberOfCassettes, machines)) ??
DEFAULT_NUMBER_OF_CASSETTES
const editing = isEditing(NAME)
const schema = Yup.object().shape({
@ -35,6 +45,18 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
.max(max)
.nullable(),
fillingPercentageCassette2: Yup.number()
.transform(transformNumber)
.integer()
.min(min)
.max(max)
.nullable(),
fiatBalanceCassette3: Yup.number()
.transform(transformNumber)
.integer()
.min(min)
.max(max)
.nullable(),
fiatBalanceCassette4: Yup.number()
.transform(transformNumber)
.integer()
.min(min)
@ -48,10 +70,10 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
validateOnChange={false}
enableReinitialize
initialValues={{
fillingPercentageCassette1:
R.path(['fillingPercentageCassette1'])(data) ?? '',
fillingPercentageCassette2:
R.path(['fillingPercentageCassette2'])(data) ?? ''
fillingPercentageCassette1: data?.fillingPercentageCassette1 ?? '',
fillingPercentageCassette2: data?.fillingPercentageCassette2 ?? '',
fillingPercentageCassette3: data?.fillingPercentageCassette3 ?? '',
fillingPercentageCassette4: data?.fillingPercentageCassette4 ?? ''
}}
validationSchema={schema}
onSubmit={it => save(section, schema.cast(it))}
@ -68,58 +90,38 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
setEditing={it => setEditing(NAME, it)}
/>
<div className={classes.wrapper}>
<div className={classes.first}>
<div className={classes.row}>
<Cashbox
labelClassName={classes.cashboxLabel}
emptyPartClassName={classes.cashboxEmptyPart}
percent={
values.fillingPercentageCassette1 ??
data?.fillingPercentageCassette1
}
applyColorVariant
applyFiatBalanceAlertsStyling
omitInnerPercentage
cashOut
/>
<div className={classes.col2}>
<TL2 className={classes.title}>Cassette 1 (Top)</TL2>
<EditableNumber
label="Alert me under"
name="fillingPercentageCassette1"
editing={editing}
displayValue={x => (x === '' ? '-' : x)}
decoration="%"
width={fieldWidth}
/>
</div>
</div>
</div>
<div className={classes.row}>
<Cashbox
labelClassName={classes.cashboxLabel}
emptyPartClassName={classes.cashboxEmptyPart}
percent={
values.fillingPercentageCassette2 ??
data?.fillingPercentageCassette2
}
applyColorVariant
applyFiatBalanceAlertsStyling
omitInnerPercentage
cashOut
/>
<div className={classes.col2}>
<TL2 className={classes.title}>Cassette 2 (Bottom)</TL2>
<EditableNumber
label="Alert me under"
name="fillingPercentageCassette2"
editing={editing}
displayValue={x => (x === '' ? '-' : x)}
decoration="%"
width={fieldWidth}
/>
</div>
</div>
{R.map(
it => (
<>
<div className={classes.row}>
<Cashbox
labelClassName={classes.cashboxLabel}
emptyPartClassName={classes.cashboxEmptyPart}
percent={
values[`fillingPercentageCassette${it + 1}`] ??
data[`cassette${it + 1}`]
}
applyColorVariant
applyFiatBalanceAlertsStyling
omitInnerPercentage
cashOut
/>
<div className={classes.col2}>
<TL2 className={classes.title}>Cassette {it + 1}</TL2>
<EditableNumber
label="Alert me under"
name={`fillingPercentageCassette${it + 1}`}
editing={editing}
displayValue={x => (x === '' ? '-' : x)}
decoration="%"
width={fieldWidth}
/>
</div>
</div>
</>
),
R.times(R.identity, maxNumberOfCassettes)
)}
</div>
</Form>
)}

View file

@ -7,14 +7,11 @@ export default {
form: {
marginBottom: 36
},
first: {
width: 236
},
title: {
marginTop: 0
},
row: {
width: 183,
width: 236,
display: 'grid',
gridTemplateColumns: 'repeat(2,1fr)',
gridTemplateRows: '1fr',

View file

@ -11,9 +11,18 @@ import NotificationsCtx from '../NotificationsContext'
const CASSETTE_1_KEY = 'fillingPercentageCassette1'
const CASSETTE_2_KEY = 'fillingPercentageCassette2'
const CASSETTE_3_KEY = 'fillingPercentageCassette3'
const CASSETTE_4_KEY = 'fillingPercentageCassette4'
const MACHINE_KEY = 'machine'
const NAME = 'fiatBalanceOverrides'
const CASSETTE_LIST = [
CASSETTE_1_KEY,
CASSETTE_2_KEY,
CASSETTE_3_KEY,
CASSETTE_4_KEY
]
const FiatBalanceOverrides = ({ section }) => {
const {
machines = [],
@ -41,42 +50,62 @@ const FiatBalanceOverrides = ({ section }) => {
const initialValues = {
[MACHINE_KEY]: null,
[CASSETTE_1_KEY]: '',
[CASSETTE_2_KEY]: ''
[CASSETTE_2_KEY]: '',
[CASSETTE_3_KEY]: '',
[CASSETTE_4_KEY]: ''
}
const maxNumberOfCassettes = Math.max(
...R.map(it => it.numberOfCassettes, machines)
)
const percentMin = 0
const percentMax = 100
const validationSchema = Yup.object().shape(
{
const validationSchema = Yup.object()
.shape({
[MACHINE_KEY]: Yup.string()
.label('Machine')
.nullable()
.required(),
[CASSETTE_1_KEY]: Yup.number()
.label('Cassette 1 (top)')
.when(CASSETTE_2_KEY, {
is: CASSETTE_2_KEY => !CASSETTE_2_KEY,
then: Yup.number().required()
})
.label('Cassette 1')
.transform(transformNumber)
.integer()
.min(percentMin)
.max(percentMax)
.nullable(),
[CASSETTE_2_KEY]: Yup.number()
.label('Cassette 2 (bottom)')
.when(CASSETTE_1_KEY, {
is: CASSETTE_1_KEY => !CASSETTE_1_KEY,
then: Yup.number().required()
})
.label('Cassette 2')
.transform(transformNumber)
.integer()
.min(percentMin)
.max(percentMax)
.nullable(),
[CASSETTE_3_KEY]: Yup.number()
.label('Cassette 3')
.transform(transformNumber)
.integer()
.min(percentMin)
.max(percentMax)
.nullable(),
[CASSETTE_4_KEY]: Yup.number()
.label('Cassette 4')
.transform(transformNumber)
.integer()
.min(percentMin)
.max(percentMax)
.nullable()
},
[CASSETTE_1_KEY, CASSETTE_2_KEY]
)
})
.test((values, context) => {
const picked = R.pick(CASSETTE_LIST, values)
if (CASSETTE_LIST.some(it => !R.isNil(picked[it]))) return
return context.createError({
path: CASSETTE_1_KEY,
message: 'At least one of the cassettes must have a value'
})
})
const viewMachine = it =>
R.compose(R.path(['name']), R.find(R.propEq('deviceId', it)))(machines)
@ -93,35 +122,35 @@ const FiatBalanceOverrides = ({ section }) => {
valueProp: 'deviceId',
labelProp: 'name'
}
},
{
name: CASSETTE_1_KEY,
header: 'Cash-out 1',
width: 155,
textAlign: 'right',
doubleHeader: 'Cash-out (Cassette Empty)',
bold: true,
input: NumberInput,
suffix: '%',
inputProps: {
decimalPlaces: 0
}
},
{
name: CASSETTE_2_KEY,
header: 'Cash-out 2',
width: 155,
textAlign: 'right',
doubleHeader: 'Cash-out (Cassette Empty)',
bold: true,
input: NumberInput,
suffix: '%',
inputProps: {
decimalPlaces: 0
}
}
]
R.until(
R.gt(R.__, maxNumberOfCassettes),
it => {
elements.push({
name: `fillingPercentageCassette${it}`,
display: `Cash-out ${it}`,
width: 155,
textAlign: 'right',
doubleHeader: 'Cash-out (Cassette Empty)',
bold: true,
input: NumberInput,
suffix: '%',
inputProps: {
decimalPlaces: 0
},
view: it => it?.toString() ?? '—',
isHidden: value =>
it >
machines.find(({ deviceId }) => deviceId === value.machine)
?.numberOfCassettes
})
return R.add(1, it)
},
1
)
return (
<EditableTable
name={NAME}

View file

@ -306,7 +306,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
) : (
errorElements
)}
{getStatus(tx) === 'Pending' && (
{tx.txClass === 'cashOut' && getStatus(tx) === 'Pending' && (
<ActionButton
color="primary"
Icon={CancelIcon}
@ -362,5 +362,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
export default memo(
DetailsRow,
(prev, next) =>
prev.it.id === next.it.id && getStatus(prev.it) === getStatus(next.it)
prev.it.id === next.it.id &&
prev.it.hasError === next.it.hasError &&
getStatus(prev.it) === getStatus(next.it)
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,85 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="316" viewBox="0 0 192 316">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-10, .cls-4 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-4, .cls-8 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #ebefff;
}
.cls-9 {
fill: #1b2559;
}
.cls-10 {
fill: #ccd8ff;
}
.cls-11 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="30.32 222.37 141.47 158 181.89 181.41 70.74 245.78 30.32 222.37"/>
<polyline class="cls-2" points="141.48 202.52 68.77 244.64 32.31 223.53 141.48 160.3"/>
<polygon class="cls-3" points="70.74 292.59 181.9 228.22 181.89 181.41 70.74 245.78 70.74 292.59"/>
<polygon class="cls-4" points="141.48 160.3 141.48 202.52 159.71 191.97 177.93 181.41 141.48 160.3"/>
</g>
<g>
<polygon class="cls-1" points="30.32 163.85 141.47 99.48 181.89 122.89 70.74 187.26 30.32 163.85"/>
<polyline class="cls-2" points="141.48 144 68.77 186.12 32.31 165.01 141.48 101.78"/>
<polygon class="cls-3" points="70.74 234.07 181.9 169.71 181.89 122.89 70.74 187.26 70.74 234.07"/>
<polygon class="cls-4" points="141.48 101.78 141.48 144 159.71 133.45 177.93 122.89 141.48 101.78"/>
</g>
<polyline class="cls-5" points="121.27 97.18 68.77 127.6 32.31 106.49 121.27 54.96"/>
<polygon class="cls-6" points="30.32 105.33 121.26 52.67 161.68 76.07 70.74 128.74 30.32 105.33"/>
<polygon class="cls-7" points="10.1 70.22 10.11 280.89 70.74 316 70.74 105.33 10.1 70.22"/>
<polygon class="cls-3" points="20.21 169.71 60.63 193.11 60.63 239.93 20.21 216.52 20.21 169.71"/>
<polygon class="cls-3" points="20.21 228.22 60.63 251.63 60.63 298.45 20.21 275.04 20.21 228.22"/>
<polygon class="cls-8" points="0 122.89 40.42 146.3 40.42 193.11 0 169.7 0 122.89"/>
<polygon class="cls-5" points="40.42 193.11 60.63 181.41 60.63 134.59 40.42 146.3 40.42 193.11"/>
<polygon class="cls-5" points="70.74 175.56 161.69 122.89 161.68 76.07 70.74 128.74 70.74 175.56"/>
<polyline class="cls-6" points="60.63 134.59 40.42 146.3 0 122.89 20.21 111.19"/>
<polyline class="cls-5" points="58.65 133.44 40.42 144 3.97 122.89 22.2 112.33"/>
<polygon class="cls-9" points="22.2 112.33 22.2 133.44 40.42 144 58.65 133.44 22.2 112.33"/>
<polygon class="cls-9" points="121.27 54.96 121.27 97.18 139.49 86.63 157.72 76.07 121.27 54.96"/>
<g>
<polygon class="cls-10" points="70.74 105.33 192 35.11 192 245.78 70.74 316 70.74 105.33"/>
<polygon class="cls-11" points="10.1 70.22 131.37 0 192 35.11 70.74 105.33 10.1 70.22"/>
</g>
<path class="cls-12" d="M13.08,161.89l4.4,2.94,0-11.26c0-.68,0-1.35,0-1.35l-.06,0a1.79,1.79,0,0,1-.88.55l-1.63.46-2.17-3.74L17.88,148l3.22,2.15v17.13l4.4,2.95v3.18l-12.42-8.31Z"/>
<path class="cls-7" d="M32.61,210.69c0-7.57,9.51-3.1,9.51-7.41a5.94,5.94,0,0,0-3.07-4.79c-2.36-1.36-3.65.41-3.65.41l-2.73-3.49s1.87-3.15,6.7-.43c3.56,2,6.57,6.06,6.57,10.2,0,7-9.08,2.71-9.18,6.67l9.5,5.92v3.44l-13.48-8.55A16.93,16.93,0,0,1,32.61,210.69Z"/>
<path class="cls-7" d="M33.74,265.64a16.52,16.52,0,0,0,4.6,4.93c2.12,1.32,3.66,1,3.66-.75,0-2.14-2-4.37-4.34-5.85l-1.4-.88-.82-2.4,3.71-2.09a7.93,7.93,0,0,1,1.46-.63v-.06s-.6-.29-1.8-1l-6-3.78v-3.17l12.38,7.72V260l-5,2.65c2.8,2.15,5.54,5.86,5.54,9.38s-2.64,5-7.16,2.2a22,22,0,0,1-6.72-6.91Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,85 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="316" viewBox="0 0 192 316">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-10, .cls-4 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-4, .cls-8 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #ebefff;
}
.cls-9 {
fill: #1b2559;
}
.cls-10 {
fill: #ccd8ff;
}
.cls-11 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="161.68 222.37 50.52 158 10.11 181.41 121.26 245.78 161.68 222.37"/>
<polyline class="cls-2" points="50.52 202.52 123.23 244.64 159.69 223.53 50.52 160.3"/>
<polygon class="cls-3" points="121.26 292.59 10.1 228.22 10.11 181.41 121.26 245.78 121.26 292.59"/>
<polygon class="cls-4" points="50.52 160.3 50.52 202.52 32.29 191.97 14.07 181.41 50.52 160.3"/>
</g>
<g>
<polygon class="cls-1" points="161.68 163.85 50.53 99.48 10.11 122.89 121.26 187.26 161.68 163.85"/>
<polyline class="cls-2" points="50.52 144 123.23 186.12 159.69 165.01 50.52 101.78"/>
<polygon class="cls-3" points="121.26 234.07 10.1 169.71 10.11 122.89 121.26 187.26 121.26 234.07"/>
<polygon class="cls-4" points="50.52 101.78 50.52 144 32.29 133.45 14.07 122.89 50.52 101.78"/>
</g>
<polyline class="cls-5" points="70.73 97.18 123.23 127.6 159.69 106.49 70.73 54.96"/>
<polygon class="cls-6" points="161.68 105.33 70.74 52.67 30.32 76.07 121.26 128.74 161.68 105.33"/>
<polygon class="cls-7" points="181.9 70.22 181.89 280.89 121.26 316 121.26 105.33 181.9 70.22"/>
<polygon class="cls-3" points="171.79 169.71 131.37 193.11 131.37 239.93 171.79 216.52 171.79 169.71"/>
<polygon class="cls-3" points="171.79 228.22 131.37 251.63 131.37 298.45 171.79 275.04 171.79 228.22"/>
<polygon class="cls-8" points="192 122.89 151.58 146.3 151.58 193.11 192 169.7 192 122.89"/>
<polygon class="cls-5" points="151.58 193.11 131.37 181.41 131.37 134.59 151.58 146.3 151.58 193.11"/>
<polygon class="cls-5" points="121.26 175.56 30.32 122.89 30.32 76.07 121.26 128.74 121.26 175.56"/>
<polyline class="cls-6" points="131.37 134.59 151.58 146.3 192 122.89 171.79 111.19"/>
<polyline class="cls-5" points="133.35 133.44 151.58 144 188.03 122.89 169.81 112.33"/>
<polygon class="cls-9" points="169.81 112.33 169.81 133.44 151.58 144 133.35 133.44 169.81 112.33"/>
<polygon class="cls-9" points="70.73 54.96 70.73 97.18 52.51 86.63 34.28 76.07 70.73 54.96"/>
<g>
<polygon class="cls-10" points="121.26 105.33 0 35.11 0 245.78 121.26 316 121.26 105.33"/>
<polygon class="cls-11" points="181.9 70.22 60.63 0 0 35.11 121.26 105.33 181.9 70.22"/>
</g>
<path class="cls-12" d="M166.87,169.18l4.35-2.47-.05-11.22c0-.68,0-1.38,0-1.38l-.05,0a10,10,0,0,1-.87,1.62l-1.59,2.44-2.12-1.11,4.95-7.64,3.14-1.74.12,17,4.35-2.47,0,3.13-12.29,7Z"/>
<path class="cls-7" d="M145.58,218.78c0-7.34,9.32-13.48,9.29-17.48,0-1.75-1.37-2.09-3-1.21-2.32,1.23-3.57,4.29-3.57,4.29l-2.68-.41a15.18,15.18,0,0,1,6.54-7.51c3.47-1.83,6.45-1.23,6.49,2.55.07,6.43-8.86,12.32-8.93,16.19l9.37-5,0,3.13-13.34,7.21A13,13,0,0,1,145.58,218.78Z"/>
<path class="cls-7" d="M147.85,270a4.94,4.94,0,0,0,4.55-.34c2.09-1.11,3.6-3.19,3.59-4.92,0-2.12-2-2.1-4.3-.87l-1.38.72-.81-1.46,3.61-6.28c.78-1.34,1.41-2.27,1.41-2.27v-.06s-.58.39-1.75,1l-5.91,3.07v-3.17l12.07-6.25,0,2.29-4.81,8.22c2.75-1,5.46-.48,5.51,3s-2.53,7.95-7,10.33c-4.28,2.28-6.66.82-6.66.82Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,89 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="316" viewBox="0 0 192 316">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-4, .cls-9 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-11, .cls-4 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #1b2559;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="30.32 222.37 141.47 158 181.89 181.41 70.74 245.78 30.32 222.37"/>
<polyline class="cls-2" points="141.48 202.52 68.77 244.64 32.31 223.53 141.48 160.3"/>
<polygon class="cls-3" points="70.74 292.59 181.9 228.22 181.89 181.41 70.74 245.78 70.74 292.59"/>
<polygon class="cls-4" points="141.48 160.3 141.48 202.52 159.71 191.97 177.93 181.41 141.48 160.3"/>
</g>
<polyline class="cls-5" points="121.27 155.7 68.77 186.12 32.31 165.01 121.27 113.48"/>
<polygon class="cls-6" points="30.32 163.85 121.26 111.18 161.68 134.59 70.74 187.26 30.32 163.85"/>
<polygon class="cls-5" points="70.74 234.07 161.69 181.41 161.68 134.59 70.74 187.26 70.74 234.07"/>
<polygon class="cls-7" points="121.27 113.48 121.27 155.7 139.5 145.15 157.72 134.59 121.27 113.48"/>
<g>
<polygon class="cls-1" points="30.32 105.33 141.47 40.96 181.89 64.37 70.74 128.74 30.32 105.33"/>
<polyline class="cls-2" points="141.48 85.48 68.77 127.6 32.31 106.49 141.48 43.26"/>
<polygon class="cls-3" points="70.74 175.55 181.9 111.18 181.89 64.37 70.74 128.74 70.74 175.55"/>
<polygon class="cls-4" points="141.48 43.26 141.48 85.48 159.71 74.92 177.93 64.37 141.48 43.26"/>
</g>
<polygon class="cls-8" points="10.11 70.22 10.11 280.89 70.74 316 70.74 105.33 10.11 70.22"/>
<g>
<polygon class="cls-9" points="70.74 105.33 192 35.11 192 245.78 70.74 316 70.74 105.33"/>
<polygon class="cls-10" points="10.11 70.22 131.37 0 192 35.11 70.74 105.33 10.11 70.22"/>
</g>
<g>
<polygon class="cls-3" points="20.21 228.22 60.63 251.63 60.63 298.45 20.21 275.04 20.21 228.22"/>
<path class="cls-8" d="M33.74,265.64a16.52,16.52,0,0,0,4.6,4.93c2.12,1.32,3.66,1,3.66-.75,0-2.14-2-4.37-4.34-5.85l-1.4-.88-.82-2.4,3.71-2.09a7.93,7.93,0,0,1,1.46-.63v-.06s-.6-.29-1.8-1l-6-3.78v-3.17l12.38,7.72V260l-5,2.65c2.8,2.15,5.54,5.86,5.54,9.38s-2.64,5-7.16,2.2a22,22,0,0,1-6.72-6.91Z"/>
</g>
<polygon class="cls-3" points="20.21 111.18 60.63 134.59 60.63 181.41 20.21 158 20.21 111.18"/>
<g id="front-drawerr">
<polyline class="cls-6" points="60.63 193.11 40.42 204.82 0 181.41 20.21 169.71"/>
<polygon class="cls-11" points="0 181.41 40.42 204.82 40.42 251.63 0 228.22 0 181.41"/>
<polygon class="cls-5" points="40.42 251.63 60.63 239.93 60.63 193.11 40.42 204.82 40.42 251.63"/>
<polyline class="cls-5" points="58.65 191.96 40.42 202.52 3.97 181.41 22.19 170.85"/>
<polygon class="cls-7" points="22.19 170.85 22.19 191.96 40.42 202.52 58.65 191.96 22.19 170.85"/>
<path class="cls-12" d="M13.39,220.52c0-7.56,9.51-3.1,9.51-7.4a6,6,0,0,0-3.07-4.79c-2.36-1.36-3.65.4-3.65.4l-2.73-3.48s1.87-3.16,6.7-.43c3.56,2,6.57,6.05,6.57,10.19,0,7.05-9.08,2.71-9.18,6.68L27,227.61V231l-13.48-8.55A19,19,0,0,1,13.39,220.52Z"/>
</g>
<path class="cls-8" d="M32.29,147.62l4.4,2.95,0-11.26c0-.69,0-1.36,0-1.36l-.06,0a1.72,1.72,0,0,1-.88.55l-1.63.45L32,135.19l5.09-1.49,3.22,2.15V153l4.4,3v3.17l-12.42-8.31Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,92 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="316" viewBox="0 0 192 316">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-4, .cls-9 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-11, .cls-4 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #1b2559;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="161.68 222.37 50.52 158 10.11 181.41 121.26 245.78 161.68 222.37"/>
<polyline class="cls-2" points="50.52 202.52 123.23 244.64 159.69 223.53 50.52 160.3"/>
<polygon class="cls-3" points="121.26 292.59 10.1 228.22 10.11 181.41 121.26 245.78 121.26 292.59"/>
<polygon class="cls-4" points="50.52 160.3 50.52 202.52 32.29 191.97 14.07 181.41 50.52 160.3"/>
</g>
<polyline class="cls-5" points="70.73 155.7 123.23 186.12 159.69 165.01 70.73 113.48"/>
<polygon class="cls-6" points="161.68 163.85 70.74 111.18 30.32 134.59 121.26 187.26 161.68 163.85"/>
<polygon class="cls-5" points="121.26 234.07 30.31 181.41 30.32 134.59 121.26 187.26 121.26 234.07"/>
<polygon class="cls-7" points="70.73 113.48 70.73 155.7 52.51 145.15 34.28 134.59 70.73 113.48"/>
<g>
<polygon class="cls-1" points="161.68 105.33 50.52 40.96 10.11 64.37 121.26 128.74 161.68 105.33"/>
<polyline class="cls-2" points="50.52 85.48 123.23 127.6 159.69 106.49 50.52 43.26"/>
<polygon class="cls-3" points="121.26 175.55 10.1 111.18 10.11 64.37 121.26 128.74 121.26 175.55"/>
<polygon class="cls-4" points="50.52 43.26 50.52 85.48 32.29 74.92 14.07 64.37 50.52 43.26"/>
</g>
<polygon class="cls-8" points="181.9 70.22 181.89 280.89 121.26 316 121.26 105.33 181.9 70.22"/>
<g>
<polygon class="cls-9" points="121.26 105.33 0 35.11 0 245.78 121.26 316 121.26 105.33"/>
<polygon class="cls-10" points="181.9 70.22 60.63 0 0 35.11 121.26 105.33 181.9 70.22"/>
</g>
<g>
<polygon class="cls-3" points="171.79 228.22 131.37 251.63 131.37 298.45 171.79 275.04 171.79 228.22"/>
<path class="cls-8" d="M147.85,270a4.94,4.94,0,0,0,4.55-.34c2.09-1.11,3.6-3.19,3.58-4.92,0-2.12-2-2.1-4.29-.87l-1.38.72-.82-1.46,3.62-6.28c.78-1.34,1.41-2.27,1.41-2.27v-.06s-.58.39-1.75,1l-5.92,3.07v-3.17l12.07-6.25,0,2.29-4.81,8.22c2.75-1,5.46-.48,5.5,3s-2.52,7.95-7,10.33c-4.27,2.28-6.65.82-6.65.82Z"/>
</g>
<polygon class="cls-3" points="171.79 111.18 131.37 134.59 131.37 181.41 171.79 158 171.79 111.18"/>
<g id="front-drawerr">
<polyline class="cls-6" points="131.37 193.11 151.58 204.82 192 181.41 171.79 169.71"/>
<polygon class="cls-11" points="192 181.41 151.58 204.82 151.58 251.63 192 228.22 192 181.41"/>
<polygon class="cls-5" points="151.58 251.63 131.37 239.93 131.37 193.11 151.58 204.82 151.58 251.63"/>
<polyline class="cls-5" points="133.35 191.96 151.58 202.52 188.03 181.41 169.81 170.85"/>
<polygon class="cls-7" points="169.81 170.85 169.81 191.96 151.58 202.52 133.35 191.96 169.81 170.85"/>
<g>
<path class="cls-12" d="M165.65,229.37c0-7.33,9.32-13.48,9.29-17.47,0-1.76-1.37-2.1-3-1.21-2.32,1.22-3.57,4.28-3.57,4.28l-2.68-.41a15.13,15.13,0,0,1,6.54-7.5c3.47-1.83,6.45-1.24,6.49,2.54.07,6.43-8.86,12.32-8.93,16.19l9.37-5,0,3.13-13.34,7.21A13,13,0,0,1,165.65,229.37Z"/>
<path class="cls-8" d="M165.65,229.37c0-7.33,9.32-13.48,9.29-17.47,0-1.76-1.37-2.1-3-1.21-2.32,1.22-3.57,4.28-3.57,4.28l-2.68-.41a15.13,15.13,0,0,1,6.54-7.5c3.47-1.83,6.45-1.24,6.49,2.54.07,6.43-8.86,12.32-8.93,16.19l9.37-5,0,3.13-13.34,7.21A13,13,0,0,1,165.65,229.37Z"/>
</g>
</g>
<path class="cls-8" d="M147.65,155,152,152.5l0-11.22c0-.68,0-1.38,0-1.38l-.06,0a9.35,9.35,0,0,1-.86,1.62L149.47,144l-2.13-1.1,4.95-7.65,3.15-1.74.12,17,4.35-2.47,0,3.13-12.29,7Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -0,0 +1,91 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="316" viewBox="0 0 192 316">
<defs>
<style>
.cls-1 {
fill: #4b5fef;
}
.cls-2 {
fill: #7687ff;
}
.cls-3 {
fill: #1b2559;
}
.cls-12, .cls-4 {
fill: #fff;
}
.cls-4, .cls-7, .cls-9 {
opacity: 0.74;
}
.cls-5 {
fill: #b8c2e6;
}
.cls-6 {
fill: #d2d8ff;
}
.cls-11, .cls-7 {
fill: #5a67ff;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polyline class="cls-1" points="121.27 214.22 68.77 244.64 32.31 223.53 121.27 172"/>
<polygon class="cls-2" points="30.32 222.37 121.26 169.7 161.68 193.11 70.74 245.78 30.32 222.37"/>
<polygon class="cls-1" points="70.74 292.59 161.69 239.93 161.68 193.11 70.74 245.78 70.74 292.59"/>
<polygon class="cls-3" points="121.27 172 121.27 214.22 139.5 203.67 157.72 193.11 121.27 172"/>
</g>
<g>
<polygon class="cls-4" points="30.32 163.85 141.47 99.48 181.89 122.89 70.74 187.26 30.32 163.85"/>
<polyline class="cls-5" points="141.48 144 68.77 186.12 32.31 165.01 141.48 101.78"/>
<polygon class="cls-6" points="70.74 234.07 181.9 169.7 181.89 122.89 70.74 187.26 70.74 234.07"/>
<polygon class="cls-7" points="141.48 101.78 141.48 144 159.71 133.44 177.93 122.89 141.48 101.78"/>
</g>
<g>
<polygon class="cls-4" points="30.32 105.33 141.47 40.96 181.89 64.37 70.74 128.74 30.32 105.33"/>
<polyline class="cls-5" points="141.48 85.48 68.77 127.6 32.31 106.49 141.48 43.26"/>
<polygon class="cls-6" points="70.74 175.55 181.9 111.18 181.89 64.37 70.74 128.74 70.74 175.55"/>
<polygon class="cls-7" points="141.48 43.26 141.48 85.48 159.71 74.92 177.93 64.37 141.48 43.26"/>
</g>
<polygon class="cls-8" points="10.1 70.22 10.11 280.89 70.74 316 70.74 105.33 10.1 70.22"/>
<g>
<polygon class="cls-9" points="70.74 105.33 192 35.11 192 245.78 70.74 316 70.74 105.33"/>
<polygon class="cls-10" points="10.1 70.22 131.37 0 192 35.11 70.74 105.33 10.1 70.22"/>
</g>
<g>
<polygon class="cls-6" points="20.21 169.71 60.63 193.11 60.63 239.93 20.21 216.52 20.21 169.71"/>
<path class="cls-8" d="M32.61,210.69c0-7.57,9.51-3.1,9.51-7.41a5.94,5.94,0,0,0-3.07-4.79c-2.36-1.36-3.65.41-3.65.41l-2.73-3.49s1.87-3.15,6.7-.43c3.56,2,6.57,6.06,6.57,10.2,0,7-9.08,2.71-9.18,6.67l9.5,5.92v3.44l-13.48-8.55A16.93,16.93,0,0,1,32.61,210.69Z"/>
</g>
<polygon class="cls-6" points="20.21 111.18 60.63 134.59 60.63 181.41 20.21 158 20.21 111.18"/>
<g id="front-drawerr">
<polyline class="cls-2" points="60.63 251.63 40.42 263.33 0 239.93 20.21 228.22"/>
<polygon class="cls-11" points="0 239.93 40.42 263.33 40.42 310.15 0 286.74 0 239.93"/>
<polygon class="cls-1" points="40.42 310.15 60.63 298.44 60.63 251.63 40.42 263.33 40.42 310.15"/>
<polyline class="cls-1" points="58.65 250.48 40.42 261.04 3.97 239.93 22.2 229.37"/>
<polygon class="cls-3" points="22.2 229.37 22.2 250.48 40.42 261.04 58.65 250.48 22.2 229.37"/>
<path class="cls-12" d="M14.55,279a16.52,16.52,0,0,0,4.6,4.93c2.12,1.32,3.66,1,3.66-.75,0-2.14-2-4.38-4.34-5.86l-1.4-.87-.82-2.4L20,271.93a8,8,0,0,1,1.45-.63v-.06s-.59-.29-1.79-1l-6-3.77v-3.17L26,271v2.32L21,276c2.8,2.14,5.54,5.86,5.54,9.38s-2.63,5-7.15,2.2a22,22,0,0,1-6.72-6.91Z"/>
</g>
<path class="cls-8" d="M32.29,147.62l4.4,2.95,0-11.26c0-.69,0-1.36,0-1.36l-.06,0a1.72,1.72,0,0,1-.88.55l-1.63.45L32,135.19l5.09-1.49,3.22,2.15V153l4.4,3v3.17l-12.42-8.31Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,91 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="316" viewBox="0 0 192 316">
<defs>
<style>
.cls-1 {
fill: #4b5fef;
}
.cls-2 {
fill: #7687ff;
}
.cls-3 {
fill: #1b2559;
}
.cls-12, .cls-4 {
fill: #fff;
}
.cls-4, .cls-7, .cls-9 {
opacity: 0.74;
}
.cls-5 {
fill: #b8c2e6;
}
.cls-6 {
fill: #d2d8ff;
}
.cls-11, .cls-7 {
fill: #5a67ff;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polyline class="cls-1" points="70.73 214.22 123.23 244.64 159.69 223.53 70.73 172"/>
<polygon class="cls-2" points="161.68 222.37 70.74 169.7 30.32 193.11 121.26 245.78 161.68 222.37"/>
<polygon class="cls-1" points="121.26 292.59 30.31 239.93 30.32 193.11 121.26 245.78 121.26 292.59"/>
<polygon class="cls-3" points="70.73 172 70.73 214.22 52.51 203.67 34.28 193.11 70.73 172"/>
</g>
<g>
<polygon class="cls-4" points="161.68 163.85 50.52 99.48 10.11 122.89 121.26 187.26 161.68 163.85"/>
<polyline class="cls-5" points="50.52 144 123.23 186.12 159.69 165.01 50.52 101.78"/>
<polygon class="cls-6" points="121.26 234.07 10.1 169.7 10.11 122.89 121.26 187.26 121.26 234.07"/>
<polygon class="cls-7" points="50.52 101.78 50.52 144 32.29 133.44 14.07 122.89 50.52 101.78"/>
</g>
<g>
<polygon class="cls-4" points="161.68 105.33 50.52 40.96 10.11 64.37 121.26 128.74 161.68 105.33"/>
<polyline class="cls-5" points="50.52 85.48 123.23 127.6 159.69 106.49 50.52 43.26"/>
<polygon class="cls-6" points="121.26 175.55 10.1 111.18 10.11 64.37 121.26 128.74 121.26 175.55"/>
<polygon class="cls-7" points="50.52 43.26 50.52 85.48 32.29 74.92 14.07 64.37 50.52 43.26"/>
</g>
<polygon class="cls-8" points="181.9 70.22 181.89 280.89 121.26 316 121.26 105.33 181.9 70.22"/>
<g>
<polygon class="cls-9" points="121.26 105.33 0 35.11 0 245.78 121.26 316 121.26 105.33"/>
<polygon class="cls-10" points="181.9 70.22 60.63 0 0 35.11 121.26 105.33 181.9 70.22"/>
</g>
<g>
<polygon class="cls-6" points="171.79 169.71 131.37 193.11 131.37 239.93 171.79 216.52 171.79 169.71"/>
<path class="cls-8" d="M145.58,218.78c0-7.34,9.32-13.48,9.28-17.48,0-1.75-1.36-2.09-3-1.21-2.32,1.23-3.57,4.29-3.57,4.29l-2.68-.41a15.18,15.18,0,0,1,6.54-7.51c3.47-1.83,6.44-1.23,6.49,2.55.07,6.43-8.86,12.32-8.93,16.19l9.37-5,0,3.13-13.34,7.21A13,13,0,0,1,145.58,218.78Z"/>
</g>
<polygon class="cls-6" points="171.79 111.18 131.37 134.59 131.37 181.41 171.79 158 171.79 111.18"/>
<g id="front-drawerr">
<polyline class="cls-2" points="131.37 251.63 151.58 263.33 192 239.93 171.79 228.22"/>
<polygon class="cls-11" points="192 239.93 151.58 263.33 151.58 310.15 192 286.74 192 239.93"/>
<polygon class="cls-1" points="151.58 310.15 131.37 298.44 131.37 251.63 151.58 263.33 151.58 310.15"/>
<polyline class="cls-1" points="133.35 250.48 151.58 261.04 188.03 239.93 169.81 229.37"/>
<polygon class="cls-3" points="169.81 229.37 169.81 250.48 151.58 261.04 133.35 250.48 169.81 229.37"/>
<path class="cls-12" d="M167.44,283.32A5,5,0,0,0,172,283c2.08-1.11,3.6-3.19,3.58-4.92,0-2.12-2-2.09-4.29-.87l-1.38.72-.82-1.45,3.62-6.29c.77-1.34,1.41-2.27,1.41-2.27v-.06a19.72,19.72,0,0,1-1.76,1l-5.91,3.07v-3.17l12.07-6.25,0,2.29L173.73,273c2.75-1,5.46-.48,5.5,3s-2.52,8-7,10.34c-4.27,2.27-6.65.81-6.65.81Z"/>
</g>
<path class="cls-8" d="M147.66,155,152,152.5l0-11.22c0-.68,0-1.38,0-1.38l-.06,0a9.35,9.35,0,0,1-.86,1.62L149.47,144l-2.13-1.1,5-7.65,3.14-1.74.12,17,4.35-2.47,0,3.13-12.29,7Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,95 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="376" viewBox="0 0 192 376">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-10, .cls-4 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-4, .cls-8 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #ebefff;
}
.cls-9 {
fill: #1b2559;
}
.cls-10 {
fill: #ccd8ff;
}
.cls-11 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="30.32 282 141.47 217.37 181.89 240.87 70.74 305.5 30.32 282"/>
<polyline class="cls-2" points="141.48 262.07 68.77 304.35 32.31 283.16 141.48 219.68"/>
<polygon class="cls-3" points="70.74 352.5 181.9 287.87 181.89 240.87 70.74 305.5 70.74 352.5"/>
<polygon class="cls-4" points="141.48 219.68 141.48 262.07 159.71 251.47 177.93 240.87 141.48 219.68"/>
</g>
<g>
<polygon class="cls-1" points="30.32 223.25 141.47 158.63 181.89 182.13 70.74 246.75 30.32 223.25"/>
<polyline class="cls-2" points="141.48 203.32 68.77 245.6 32.31 224.41 141.48 160.93"/>
<polygon class="cls-3" points="70.74 293.75 181.9 229.13 181.89 182.13 70.74 246.75 70.74 293.75"/>
<polygon class="cls-4" points="141.48 160.93 141.48 203.32 159.71 192.72 177.93 182.13 141.48 160.93"/>
</g>
<g>
<polygon class="cls-1" points="30.32 164.5 141.47 99.88 181.89 123.38 70.74 188 30.32 164.5"/>
<polyline class="cls-2" points="141.48 144.57 68.77 186.85 32.31 165.66 141.48 102.18"/>
<polygon class="cls-3" points="70.74 235 181.9 170.38 181.89 123.38 70.74 188 70.74 235"/>
<polygon class="cls-4" points="141.48 102.18 141.48 144.57 159.7 133.97 177.93 123.38 141.48 102.18"/>
</g>
<polyline class="cls-5" points="121.27 97.57 68.77 128.1 32.31 106.91 121.27 55.18"/>
<polygon class="cls-6" points="30.32 105.75 121.26 52.87 161.68 76.38 70.73 129.25 30.32 105.75"/>
<polygon class="cls-7" points="10.11 70.5 10.11 340.75 70.75 376 70.74 105.75 10.11 70.5"/>
<polygon class="cls-3" points="20.21 170.38 60.63 193.88 60.63 240.88 20.21 217.38 20.21 170.38"/>
<polygon class="cls-8" points="0 123.38 40.42 146.88 40.42 193.88 0 170.37 0 123.38"/>
<polygon class="cls-5" points="40.42 193.88 60.63 182.13 60.63 135.13 40.42 146.88 40.42 193.88"/>
<polygon class="cls-5" points="70.74 176.25 161.68 123.38 161.68 76.38 70.74 129.25 70.74 176.25"/>
<polyline class="cls-6" points="60.63 135.13 40.42 146.88 0 123.38 20.21 111.63"/>
<polyline class="cls-5" points="58.65 133.97 40.42 144.57 3.97 123.38 22.2 112.78"/>
<polygon class="cls-9" points="22.2 112.78 22.2 133.97 40.42 144.57 58.65 133.97 22.2 112.78"/>
<polygon class="cls-9" points="121.27 55.18 121.27 97.57 139.49 86.97 157.72 76.37 121.27 55.18"/>
<g>
<polygon class="cls-10" points="70.73 105.75 192 35.25 192 305.5 70.74 376 70.73 105.75"/>
<polygon class="cls-11" points="10.1 70.5 131.37 0 192 35.25 70.73 105.75 10.1 70.5"/>
</g>
<g>
<polygon class="cls-3" points="20.21 229.12 60.63 252.62 60.63 299.62 20.21 276.12 20.21 229.12"/>
<path class="cls-7" d="M33.74,265.64a16.52,16.52,0,0,0,4.6,4.93c2.12,1.32,3.66,1,3.66-.75,0-2.14-2-4.37-4.34-5.85l-1.4-.88-.82-2.4,3.71-2.09a7.93,7.93,0,0,1,1.46-.63v-.06s-.6-.29-1.8-1l-6-3.78v-3.17l12.38,7.72V260l-5,2.65c2.8,2.15,5.54,5.86,5.54,9.38s-2.64,5-7.16,2.2a22,22,0,0,1-6.72-6.91Z"/>
</g>
<polygon class="cls-3" points="20.21 287.87 60.63 311.37 60.63 358.37 20.21 334.87 20.21 287.87"/>
<path class="cls-7" d="M41.49,327.94v-12l-4.36-2.35-8.76,8.08v2.26L37.86,329v5.23l3.63,2V331l2.63,1.42v-3.06Zm-3.62-8.25V326l-5.61-3v-.05l4.7-4a5.87,5.87,0,0,0,1-1.32l.06,0A16.49,16.49,0,0,0,37.87,319.69Z"/>
<path class="cls-7" d="M32.61,210.69c0-7.57,9.51-3.1,9.51-7.41a5.94,5.94,0,0,0-3.07-4.79c-2.36-1.36-3.65.41-3.65.41l-2.73-3.49s1.87-3.15,6.7-.43c3.56,2,6.57,6.06,6.57,10.2,0,7-9.08,2.71-9.18,6.67l9.5,5.92v3.44l-13.48-8.55A16.93,16.93,0,0,1,32.61,210.69Z"/>
<path class="cls-12" d="M13.08,161.89l4.4,2.94,0-11.26c0-.68,0-1.35,0-1.35l-.06,0a1.79,1.79,0,0,1-.88.55l-1.63.46-2.17-3.74L17.88,148l3.22,2.15v17.13l4.4,2.95v3.18l-12.42-8.31Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,95 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="376" viewBox="0 0 192 376">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-10, .cls-4 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-4, .cls-8 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #ebefff;
}
.cls-9 {
fill: #1b2559;
}
.cls-10 {
fill: #ccd8ff;
}
.cls-11 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="161.68 282 50.53 217.37 10.11 240.87 121.26 305.5 161.68 282"/>
<polyline class="cls-2" points="50.52 262.07 123.23 304.35 159.69 283.16 50.52 219.68"/>
<polygon class="cls-3" points="121.26 352.5 10.1 287.87 10.11 240.87 121.26 305.5 121.26 352.5"/>
<polygon class="cls-4" points="50.52 219.68 50.52 262.07 32.3 251.47 14.07 240.87 50.52 219.68"/>
</g>
<g>
<polygon class="cls-1" points="161.68 223.25 50.53 158.63 10.11 182.13 121.26 246.75 161.68 223.25"/>
<polyline class="cls-2" points="50.52 203.32 123.23 245.6 159.69 224.41 50.52 160.93"/>
<polygon class="cls-3" points="121.26 293.75 10.1 229.13 10.11 182.13 121.26 246.75 121.26 293.75"/>
<polygon class="cls-4" points="50.52 160.93 50.52 203.32 32.3 192.72 14.07 182.13 50.52 160.93"/>
</g>
<g>
<polygon class="cls-1" points="161.68 164.5 50.53 99.88 10.11 123.38 121.26 188 161.68 164.5"/>
<polyline class="cls-2" points="50.52 144.57 123.23 186.85 159.69 165.66 50.52 102.18"/>
<polygon class="cls-3" points="121.26 235 10.1 170.38 10.11 123.38 121.26 188 121.26 235"/>
<polygon class="cls-4" points="50.52 102.18 50.52 144.57 32.3 133.97 14.07 123.38 50.52 102.18"/>
</g>
<polyline class="cls-5" points="70.73 97.57 123.23 128.1 159.69 106.91 70.73 55.18"/>
<polygon class="cls-6" points="161.68 105.75 70.74 52.87 30.32 76.38 121.27 129.25 161.68 105.75"/>
<polygon class="cls-7" points="181.89 70.5 181.89 340.75 121.25 376 121.26 105.75 181.89 70.5"/>
<polygon class="cls-3" points="171.79 170.38 131.37 193.88 131.37 240.88 171.79 217.38 171.79 170.38"/>
<polygon class="cls-8" points="192 123.38 151.58 146.88 151.58 193.88 192 170.37 192 123.38"/>
<polygon class="cls-5" points="151.58 193.88 131.37 182.13 131.37 135.13 151.58 146.88 151.58 193.88"/>
<polygon class="cls-5" points="121.26 176.25 30.32 123.38 30.32 76.38 121.26 129.25 121.26 176.25"/>
<polyline class="cls-6" points="131.37 135.13 151.58 146.88 192 123.38 171.79 111.63"/>
<polyline class="cls-5" points="133.35 133.97 151.58 144.57 188.03 123.38 169.81 112.78"/>
<polygon class="cls-9" points="169.81 112.78 169.81 133.97 151.58 144.57 133.35 133.97 169.81 112.78"/>
<polygon class="cls-9" points="70.73 55.18 70.73 97.57 52.51 86.97 34.28 76.37 70.73 55.18"/>
<g>
<polygon class="cls-10" points="121.27 105.75 0 35.25 0 305.5 121.26 376 121.27 105.75"/>
<polygon class="cls-11" points="181.9 70.5 60.63 0 0 35.25 121.27 105.75 181.9 70.5"/>
</g>
<path class="cls-12" d="M166.87,169.85l4.35-2.48-.05-11.27c0-.68,0-1.38,0-1.38l-.05,0a9.32,9.32,0,0,1-.87,1.63l-1.59,2.44-2.12-1.11,4.95-7.67,3.14-1.75.12,17.05,4.35-2.48,0,3.14-12.29,7Z"/>
<path class="cls-7" d="M145.58,219.64c0-7.36,9.32-13.53,9.29-17.54,0-1.76-1.37-2.11-3-1.22-2.32,1.23-3.56,4.3-3.56,4.3l-2.69-.41a15.23,15.23,0,0,1,6.54-7.53c3.47-1.84,6.45-1.24,6.49,2.55.07,6.46-8.86,12.38-8.93,16.26l9.37-5.07,0,3.14-13.34,7.24A13.34,13.34,0,0,1,145.58,219.64Z"/>
<g>
<polygon class="cls-3" points="171.79 229.12 131.37 252.62 131.37 299.62 171.79 276.12 171.79 229.12"/>
<path class="cls-7" d="M147.85,271.1a4.93,4.93,0,0,0,4.55-.35c2.09-1.11,3.6-3.2,3.59-4.93,0-2.13-2-2.11-4.3-.88l-1.38.73-.81-1.47,3.61-6.3c.78-1.35,1.41-2.29,1.41-2.29v-.05s-.58.39-1.75,1l-5.91,3.08v-3.19l12.07-6.27,0,2.3-4.81,8.25c2.75-1,5.46-.47,5.51,3s-2.53,8-7,10.37c-4.28,2.28-6.66.82-6.66.82Z"/>
</g>
<polygon class="cls-3" points="171.79 287.87 131.37 311.37 131.37 358.37 171.79 334.87 171.79 287.87"/>
<path class="cls-7" d="M145.25,330.84l8.51-16.71,4.26-1.91.14,11.92,2.58-1.18,0,3-2.59,1.19.06,5.2-3.6,1.66,0-5.21-9.36,4.29Zm9.34-5.07-.06-6.27a20.14,20.14,0,0,1,.13-2.17l-.06,0a20.39,20.39,0,0,1-1,2.29l-4.56,8.58v.06Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,97 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="376" viewBox="0 0 192 376">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-4, .cls-9 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-11, .cls-4 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #1b2559;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="30.32 282 141.47 217.37 181.89 240.87 70.74 305.5 30.32 282"/>
<polyline class="cls-2" points="141.48 262.06 68.77 304.35 32.31 283.16 141.48 219.68"/>
<polygon class="cls-3" points="70.74 352.5 181.9 287.87 181.89 240.87 70.74 305.5 70.74 352.5"/>
<polygon class="cls-4" points="141.48 219.68 141.48 262.06 159.71 251.47 177.93 240.87 141.48 219.68"/>
</g>
<g>
<polygon class="cls-1" points="30.32 223.25 141.47 158.63 181.89 182.13 70.74 246.75 30.32 223.25"/>
<polyline class="cls-2" points="141.48 203.32 68.77 245.6 32.31 224.41 141.48 160.93"/>
<polygon class="cls-3" points="70.74 293.75 181.9 229.13 181.89 182.13 70.74 246.75 70.74 293.75"/>
<polygon class="cls-4" points="141.48 160.93 141.48 203.32 159.71 192.72 177.93 182.13 141.48 160.93"/>
</g>
<polyline class="cls-5" points="121.27 156.31 68.77 186.85 32.31 165.66 121.27 113.93"/>
<polygon class="cls-6" points="30.32 164.5 121.26 111.62 161.68 135.12 70.74 188 30.32 164.5"/>
<polygon class="cls-5" points="70.74 235 161.69 182.12 161.68 135.12 70.74 188 70.74 235"/>
<polygon class="cls-7" points="121.27 113.93 121.27 156.31 139.5 145.72 157.72 135.12 121.27 113.93"/>
<g>
<polygon class="cls-1" points="30.32 105.75 141.47 41.12 181.89 64.62 70.74 129.25 30.32 105.75"/>
<polyline class="cls-2" points="141.48 85.81 68.77 128.1 32.31 106.91 141.48 43.43"/>
<polygon class="cls-3" points="70.74 176.25 181.9 111.62 181.89 64.62 70.74 129.25 70.74 176.25"/>
<polygon class="cls-4" points="141.48 43.43 141.48 85.81 159.71 75.22 177.93 64.62 141.48 43.43"/>
</g>
<polygon class="cls-8" points="10.11 70.5 10.12 340.75 70.75 376 70.74 105.75 10.11 70.5"/>
<g>
<polygon class="cls-9" points="70.74 105.75 192 35.25 192 305.5 70.74 376 70.74 105.75"/>
<polygon class="cls-10" points="10.11 70.5 131.37 0 192 35.25 70.74 105.75 10.11 70.5"/>
</g>
<g>
<polygon class="cls-3" points="20.21 229.12 60.63 252.62 60.63 299.62 20.21 276.12 20.21 229.12"/>
<path class="cls-8" d="M33.74,265.64a16.52,16.52,0,0,0,4.6,4.93c2.12,1.32,3.66,1,3.66-.75,0-2.14-2-4.37-4.34-5.85l-1.4-.88-.82-2.4,3.71-2.09a7.93,7.93,0,0,1,1.46-.63v-.06s-.6-.29-1.8-1l-6-3.78v-3.17l12.38,7.72V260l-5,2.65c2.8,2.15,5.54,5.86,5.54,9.38s-2.64,5-7.16,2.2a22,22,0,0,1-6.72-6.91Z"/>
</g>
<polygon class="cls-3" points="20.21 111.62 60.63 135.12 60.63 182.12 20.21 158.62 20.21 111.62"/>
<g id="front-drawerr">
<polyline class="cls-6" points="60.63 193.88 40.42 205.63 0 182.13 20.21 170.38"/>
<polygon class="cls-11" points="0 182.13 40.42 205.63 40.42 252.63 0 229.13 0 182.13"/>
<polygon class="cls-5" points="40.42 252.63 60.63 240.88 60.63 193.88 40.42 205.63 40.42 252.63"/>
<polyline class="cls-5" points="58.65 192.72 40.42 203.32 3.97 182.13 22.19 171.53"/>
<polygon class="cls-7" points="22.19 171.53 22.19 192.72 40.42 203.32 58.65 192.72 22.19 171.53"/>
<path class="cls-12" d="M13.39,220.52c0-7.56,9.51-3.1,9.51-7.4a6,6,0,0,0-3.07-4.79c-2.36-1.36-3.65.4-3.65.4l-2.73-3.48s1.87-3.16,6.7-.43c3.56,2,6.57,6.05,6.57,10.19,0,7.05-9.08,2.71-9.18,6.68L27,227.61V231l-13.48-8.55A19,19,0,0,1,13.39,220.52Z"/>
</g>
<polygon class="cls-3" points="20.21 287.87 60.64 311.37 60.64 358.37 20.21 334.87 20.21 287.87"/>
<path class="cls-8" d="M32.29,147.62l4.4,2.95,0-11.26c0-.69,0-1.36,0-1.36l-.06,0a1.72,1.72,0,0,1-.88.55l-1.63.45L32,135.19l5.09-1.49,3.22,2.15V153l4.4,3v3.17l-12.42-8.31Z"/>
<path class="cls-8" d="M41.49,327.94v-12l-4.36-2.35-8.76,8.08v2.26L37.86,329v5.23l3.63,2V331l2.63,1.42v-3.06Zm-3.62-8.25V326l-5.61-3v-.05l4.7-4a5.87,5.87,0,0,0,1-1.32l.06,0A16.49,16.49,0,0,0,37.87,319.69Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,100 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="376" viewBox="0 0 192 376">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-4, .cls-9 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-11, .cls-4 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #1b2559;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="161.68 282 50.52 217.37 10.11 240.87 121.26 305.5 161.68 282"/>
<polyline class="cls-2" points="50.52 262.06 123.23 304.35 159.69 283.16 50.52 219.68"/>
<polygon class="cls-3" points="121.26 352.5 10.1 287.87 10.11 240.87 121.26 305.5 121.26 352.5"/>
<polygon class="cls-4" points="50.52 219.68 50.52 262.06 32.29 251.47 14.07 240.87 50.52 219.68"/>
</g>
<g>
<polygon class="cls-1" points="161.68 223.25 50.52 158.63 10.11 182.13 121.26 246.75 161.68 223.25"/>
<polyline class="cls-2" points="50.52 203.32 123.23 245.6 159.69 224.41 50.52 160.93"/>
<polygon class="cls-3" points="121.26 293.75 10.1 229.13 10.11 182.13 121.26 246.75 121.26 293.75"/>
<polygon class="cls-4" points="50.52 160.93 50.52 203.32 32.29 192.72 14.07 182.13 50.52 160.93"/>
</g>
<polyline class="cls-5" points="70.73 156.31 123.23 186.85 159.69 165.66 70.73 113.93"/>
<polygon class="cls-6" points="161.68 164.5 70.74 111.62 30.32 135.12 121.26 188 161.68 164.5"/>
<polygon class="cls-5" points="121.26 235 30.31 182.12 30.32 135.12 121.26 188 121.26 235"/>
<polygon class="cls-7" points="70.73 113.93 70.73 156.31 52.51 145.72 34.28 135.12 70.73 113.93"/>
<g>
<polygon class="cls-1" points="161.68 105.75 50.52 41.12 10.11 64.62 121.26 129.25 161.68 105.75"/>
<polyline class="cls-2" points="50.52 85.81 123.23 128.1 159.69 106.91 50.52 43.43"/>
<polygon class="cls-3" points="121.26 176.25 10.1 111.62 10.11 64.62 121.26 129.25 121.26 176.25"/>
<polygon class="cls-4" points="50.52 43.43 50.52 85.81 32.29 75.22 14.07 64.62 50.52 43.43"/>
</g>
<polygon class="cls-8" points="181.89 70.5 181.88 340.75 121.25 376 121.26 105.75 181.89 70.5"/>
<g>
<polygon class="cls-9" points="121.26 105.75 0 35.25 0 305.5 121.26 376 121.26 105.75"/>
<polygon class="cls-10" points="181.9 70.5 60.63 0 0 35.25 121.26 105.75 181.9 70.5"/>
</g>
<g>
<polygon class="cls-3" points="171.79 229.12 131.37 252.62 131.37 299.62 171.79 276.12 171.79 229.12"/>
<path class="cls-8" d="M147.85,271.1a4.92,4.92,0,0,0,4.55-.35c2.09-1.11,3.6-3.2,3.58-4.93,0-2.13-2-2.11-4.29-.88l-1.38.73-.82-1.47,3.62-6.3c.78-1.35,1.41-2.29,1.41-2.29v-.05s-.58.39-1.75,1l-5.92,3.08v-3.19l12.07-6.27,0,2.3-4.81,8.25c2.75-1,5.46-.47,5.5,3s-2.52,8-7,10.37c-4.27,2.28-6.65.82-6.65.82Z"/>
</g>
<polygon class="cls-3" points="171.79 111.62 131.37 135.12 131.37 182.12 171.79 158.62 171.79 111.62"/>
<g id="front-drawerr">
<polyline class="cls-6" points="131.37 193.88 151.58 205.63 192 182.13 171.79 170.38"/>
<polygon class="cls-11" points="192 182.13 151.58 205.63 151.58 252.63 192 229.13 192 182.13"/>
<polygon class="cls-5" points="151.58 252.63 131.37 240.88 131.37 193.88 151.58 205.63 151.58 252.63"/>
<polyline class="cls-5" points="133.35 192.72 151.58 203.32 188.03 182.13 169.81 171.53"/>
<polygon class="cls-7" points="169.81 171.53 169.81 192.72 151.58 203.32 133.35 192.72 169.81 171.53"/>
<g>
<path class="cls-12" d="M165.65,230.28c0-7.37,9.32-13.53,9.29-17.55,0-1.76-1.37-2.1-3-1.21-2.32,1.23-3.57,4.3-3.57,4.3l-2.68-.41a15.23,15.23,0,0,1,6.54-7.54c3.47-1.83,6.45-1.24,6.49,2.56.07,6.45-8.86,12.37-8.93,16.25l9.37-5.07,0,3.15L165.82,232A13.34,13.34,0,0,1,165.65,230.28Z"/>
<path class="cls-8" d="M165.65,230.28c0-7.37,9.32-13.53,9.29-17.55,0-1.76-1.37-2.1-3-1.21-2.32,1.23-3.57,4.3-3.57,4.3l-2.68-.41a15.23,15.23,0,0,1,6.54-7.54c3.47-1.83,6.45-1.24,6.49,2.56.07,6.45-8.86,12.37-8.93,16.25l9.37-5.07,0,3.15L165.82,232A13.34,13.34,0,0,1,165.65,230.28Z"/>
</g>
</g>
<path class="cls-8" d="M147.65,155.58,152,153.1l0-11.26c0-.69,0-1.39,0-1.39l-.06,0a9.6,9.6,0,0,1-.86,1.63l-1.59,2.45-2.13-1.11,4.95-7.67,3.15-1.75.12,17.05,4.35-2.48,0,3.14-12.29,7Z"/>
<polygon class="cls-3" points="171.78 287.87 131.36 311.37 131.36 358.37 171.78 334.87 171.78 287.87"/>
<path class="cls-8" d="M145.25,330.84l8.51-16.71,4.25-1.91.14,11.92,2.59-1.18,0,3-2.59,1.19.06,5.19-3.6,1.66,0-5.21-9.36,4.29Zm9.33-5.07-.05-6.27a21.54,21.54,0,0,1,.12-2.17l-.05,0a19.36,19.36,0,0,1-1,2.29l-4.56,8.58v.06Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,97 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="376" viewBox="0 0 192 376">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-4, .cls-9 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-11, .cls-4 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #1b2559;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="30.33 282 141.48 217.38 181.89 240.88 70.74 305.5 30.33 282"/>
<polyline class="cls-2" points="141.48 262.07 68.77 304.35 32.32 283.16 141.48 219.68"/>
<polygon class="cls-3" points="70.74 352.5 181.9 287.88 181.89 240.88 70.74 305.5 70.74 352.5"/>
<polygon class="cls-4" points="141.48 219.68 141.48 262.07 159.71 251.47 177.93 240.88 141.48 219.68"/>
</g>
<g>
<polyline class="cls-5" points="121.26 215.07 68.77 245.6 32.31 224.41 121.26 172.68"/>
<polygon class="cls-6" points="30.32 223.25 121.26 170.37 161.68 193.88 70.73 246.75 30.32 223.25"/>
<polygon class="cls-5" points="70.74 293.75 161.68 240.88 161.68 193.88 70.74 246.75 70.74 293.75"/>
<polygon class="cls-7" points="121.26 172.68 121.26 215.07 139.49 204.47 157.72 193.87 121.26 172.68"/>
</g>
<g>
<polygon class="cls-1" points="30.32 164.5 141.47 99.87 181.89 123.37 70.73 188 30.32 164.5"/>
<polyline class="cls-2" points="141.47 144.56 68.77 186.85 32.31 165.66 141.47 102.18"/>
<polygon class="cls-3" points="70.74 235 181.89 170.37 181.89 123.37 70.74 188 70.74 235"/>
<polygon class="cls-4" points="141.47 102.18 141.47 144.56 159.7 133.97 177.93 123.37 141.47 102.18"/>
</g>
<g>
<polygon class="cls-1" points="30.32 105.75 141.47 41.12 181.89 64.62 70.73 129.25 30.32 105.75"/>
<polyline class="cls-2" points="141.47 85.81 68.77 128.1 32.31 106.91 141.47 43.43"/>
<polygon class="cls-3" points="70.74 176.25 181.89 111.62 181.89 64.62 70.74 129.25 70.74 176.25"/>
<polygon class="cls-4" points="141.47 43.43 141.47 85.81 159.7 75.22 177.93 64.62 141.47 43.43"/>
</g>
<polygon class="cls-8" points="10.1 70.5 10.11 340.75 70.74 376 70.73 105.75 10.1 70.5"/>
<polygon class="cls-3" points="20.21 170.38 60.63 193.88 60.63 240.88 20.21 217.38 20.21 170.38"/>
<polygon class="cls-3" points="20.21 111.62 60.63 135.12 60.63 182.12 20.21 158.62 20.21 111.62"/>
<g>
<polygon class="cls-9" points="70.74 105.75 192 35.25 192 305.5 70.74 376 70.74 105.75"/>
<polygon class="cls-10" points="10.11 70.5 131.37 0 192 35.25 70.74 105.75 10.11 70.5"/>
</g>
<polygon class="cls-3" points="20.22 287.88 60.64 311.38 60.64 358.38 20.22 334.88 20.22 287.88"/>
<path class="cls-8" d="M41.49,327.94v-12l-4.36-2.35-8.76,8.08v2.26L37.86,329v5.23l3.63,2V331l2.63,1.42v-3.06Zm-3.62-8.25V326l-5.61-3v-.05l4.7-4a5.87,5.87,0,0,0,1-1.32l.06,0A16.49,16.49,0,0,0,37.87,319.69Z"/>
<g id="front-drawerr">
<polyline class="cls-6" points="60.63 252.62 40.42 264.37 0 240.87 20.21 229.12"/>
<polygon class="cls-11" points="0 240.87 40.42 264.37 40.42 311.37 0 287.87 0 240.87"/>
<polygon class="cls-5" points="40.42 311.37 60.63 299.62 60.63 252.62 40.42 264.37 40.42 311.37"/>
<polyline class="cls-5" points="58.65 251.47 40.42 262.07 3.97 240.87 22.19 230.28"/>
<polygon class="cls-7" points="22.19 230.28 22.19 251.47 40.42 262.07 58.65 251.47 22.19 230.28"/>
</g>
<path class="cls-8" d="M32.29,147.62l4.4,2.95,0-11.26c0-.69,0-1.36,0-1.36l-.06,0a1.72,1.72,0,0,1-.88.55l-1.63.45L32,135.19l5.09-1.49,3.22,2.15V153l4.4,3v3.17l-12.42-8.31Z"/>
<path class="cls-12" d="M14.55,279a16.52,16.52,0,0,0,4.6,4.93c2.12,1.32,3.66,1,3.66-.75,0-2.14-2-4.38-4.34-5.86l-1.4-.87-.82-2.4L20,271.93a8,8,0,0,1,1.45-.63v-.06s-.59-.29-1.79-1l-6-3.77v-3.17L26,271v2.32L21,276c2.8,2.14,5.54,5.86,5.54,9.38s-2.63,5-7.15,2.2a22,22,0,0,1-6.72-6.91Z"/>
<path class="cls-8" d="M32.61,210.69c0-7.57,9.51-3.1,9.51-7.41a5.94,5.94,0,0,0-3.07-4.79c-2.36-1.36-3.65.41-3.65.41l-2.73-3.49s1.87-3.15,6.7-.43c3.56,2,6.57,6.06,6.57,10.2,0,7-9.08,2.71-9.18,6.67l9.5,5.92v3.44l-13.48-8.55A16.93,16.93,0,0,1,32.61,210.69Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,99 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="376" viewBox="0 0 192 376">
<defs>
<style>
.cls-1, .cls-12 {
fill: #fff;
}
.cls-1, .cls-4, .cls-9 {
opacity: 0.74;
}
.cls-2 {
fill: #b8c2e6;
}
.cls-3 {
fill: #d2d8ff;
}
.cls-11, .cls-4 {
fill: #5a67ff;
}
.cls-5 {
fill: #4b5fef;
}
.cls-6 {
fill: #7687ff;
}
.cls-7 {
fill: #1b2559;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polygon class="cls-1" points="161.67 282 50.52 217.38 10.11 240.88 121.26 305.5 161.67 282"/>
<polyline class="cls-2" points="50.52 262.07 123.23 304.35 159.68 283.16 50.52 219.68"/>
<polygon class="cls-3" points="121.26 352.5 10.1 287.88 10.11 240.88 121.26 305.5 121.26 352.5"/>
<polygon class="cls-4" points="50.52 219.68 50.52 262.07 32.29 251.47 14.07 240.88 50.52 219.68"/>
</g>
<g>
<polyline class="cls-5" points="70.74 215.07 123.23 245.6 159.69 224.41 70.74 172.68"/>
<polygon class="cls-6" points="161.68 223.25 70.74 170.37 30.32 193.88 121.27 246.75 161.68 223.25"/>
<polygon class="cls-5" points="121.26 293.75 30.32 240.88 30.32 193.88 121.26 246.75 121.26 293.75"/>
<polygon class="cls-7" points="70.74 172.68 70.74 215.07 52.51 204.47 34.28 193.87 70.74 172.68"/>
</g>
<g>
<polygon class="cls-1" points="161.68 164.5 50.53 99.87 10.11 123.37 121.27 188 161.68 164.5"/>
<polyline class="cls-2" points="50.53 144.56 123.23 186.85 159.69 165.66 50.53 102.18"/>
<polygon class="cls-3" points="121.26 235 10.11 170.37 10.11 123.37 121.26 188 121.26 235"/>
<polygon class="cls-4" points="50.53 102.18 50.53 144.56 32.3 133.97 14.07 123.37 50.53 102.18"/>
</g>
<g>
<polygon class="cls-1" points="161.68 105.75 50.53 41.12 10.11 64.62 121.27 129.25 161.68 105.75"/>
<polyline class="cls-2" points="50.53 85.81 123.23 128.1 159.69 106.91 50.53 43.43"/>
<polygon class="cls-3" points="121.26 176.25 10.11 111.62 10.11 64.62 121.26 129.25 121.26 176.25"/>
<polygon class="cls-4" points="50.53 43.43 50.53 85.81 32.3 75.22 14.07 64.62 50.53 43.43"/>
</g>
<polygon class="cls-8" points="181.9 70.5 181.89 340.75 121.26 376 121.27 105.75 181.9 70.5"/>
<g>
<polygon class="cls-3" points="171.79 170.38 131.37 193.88 131.37 240.88 171.79 217.38 171.79 170.38"/>
<path class="cls-8" d="M145.58,219.64c0-7.36,9.32-13.53,9.29-17.54,0-1.76-1.37-2.11-3-1.22-2.31,1.23-3.56,4.31-3.56,4.31l-2.69-.42a15.23,15.23,0,0,1,6.54-7.53c3.47-1.84,6.45-1.24,6.49,2.55.07,6.46-8.86,12.38-8.93,16.26l9.37-5.07,0,3.14-13.34,7.24A13.17,13.17,0,0,1,145.58,219.64Z"/>
</g>
<polygon class="cls-3" points="171.79 111.62 131.37 135.12 131.37 182.12 171.79 158.62 171.79 111.62"/>
<path class="cls-8" d="M147.66,155.58,152,153.1l0-11.26c0-.69,0-1.39,0-1.39l0,0a10.31,10.31,0,0,1-.87,1.63l-1.59,2.45-2.12-1.11,5-7.67,3.14-1.75.12,17.05,4.35-2.48,0,3.14-12.29,7Z"/>
<g>
<polygon class="cls-9" points="121.26 105.75 0 35.25 0 305.5 121.26 376 121.26 105.75"/>
<polygon class="cls-10" points="181.89 70.5 60.63 0 0 35.25 121.26 105.75 181.89 70.5"/>
</g>
<polygon class="cls-3" points="171.78 287.88 131.36 311.38 131.36 358.38 171.78 334.88 171.78 287.88"/>
<path class="cls-8" d="M145.25,330.84l8.5-16.71,4.26-1.91.14,11.92,2.58-1.18.05,3-2.6,1.19.06,5.19L154.65,334l-.05-5.21-9.35,4.29Zm9.33-5.07-.05-6.26a21.61,21.61,0,0,1,.12-2.18l-.06,0a20.39,20.39,0,0,1-1,2.29l-4.56,8.58v.06Z"/>
<g id="front-drawerr">
<polyline class="cls-6" points="131.37 252.62 151.58 264.37 192 240.87 171.79 229.12"/>
<polygon class="cls-11" points="192 240.87 151.58 264.37 151.58 311.37 192 287.87 192 240.87"/>
<polygon class="cls-5" points="151.58 311.37 131.37 299.62 131.37 252.62 151.58 264.37 151.58 311.37"/>
<polyline class="cls-5" points="133.35 251.47 151.58 262.07 188.03 240.87 169.81 230.28"/>
<polygon class="cls-7" points="169.81 230.28 169.81 251.47 151.58 262.07 133.35 251.47 169.81 230.28"/>
<path class="cls-12" d="M167.44,284.44a4.92,4.92,0,0,0,4.55-.35c2.09-1.11,3.6-3.2,3.58-4.94,0-2.13-2-2.1-4.29-.87l-1.38.72-.82-1.46,3.62-6.31c.77-1.35,1.41-2.28,1.41-2.28v-.06s-.58.39-1.75,1L166.44,273v-3.19l12.07-6.27,0,2.29-4.81,8.26c2.75-1.05,5.46-.48,5.5,3s-2.52,8-7,10.38c-4.27,2.28-6.65.82-6.65.82Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,101 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="376.47" viewBox="0 0 192 376.47">
<defs>
<style>
.cls-1 {
fill: #4b5fef;
}
.cls-2 {
fill: #7687ff;
}
.cls-3 {
fill: #1b2559;
}
.cls-12, .cls-4 {
fill: #fff;
}
.cls-4, .cls-7, .cls-9 {
opacity: 0.74;
}
.cls-5 {
fill: #b8c2e6;
}
.cls-6 {
fill: #d2d8ff;
}
.cls-11, .cls-7 {
fill: #5a67ff;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polyline class="cls-1" points="121.26 274.16 68.77 304.73 32.31 283.51 121.26 231.72"/>
<polygon class="cls-2" points="30.32 282.35 121.26 229.41 161.68 252.94 70.73 305.88 30.32 282.35"/>
<polygon class="cls-1" points="70.74 352.94 161.68 300 161.68 252.94 70.74 305.88 70.74 352.94"/>
<polygon class="cls-3" points="121.26 231.72 121.26 274.16 139.49 263.55 157.72 252.94 121.26 231.72"/>
</g>
<g>
<polygon class="cls-4" points="30.33 223.53 141.48 158.82 181.89 182.35 70.74 247.06 30.33 223.53"/>
<polyline class="cls-5" points="141.48 203.57 68.77 245.91 32.32 224.69 141.48 161.13"/>
<polygon class="cls-6" points="70.74 294.12 181.9 229.41 181.89 182.35 70.74 247.06 70.74 294.12"/>
<polygon class="cls-7" points="141.48 161.13 141.48 203.57 159.71 192.96 177.93 182.35 141.48 161.13"/>
</g>
<g>
<polygon class="cls-4" points="30.32 164.7 141.47 100 181.89 123.53 70.73 188.23 30.32 164.7"/>
<polyline class="cls-5" points="141.47 144.74 68.77 187.08 32.31 165.86 141.47 102.31"/>
<polygon class="cls-6" points="70.74 235.29 181.89 170.59 181.89 123.53 70.74 188.23 70.74 235.29"/>
<polygon class="cls-7" points="141.47 102.31 141.47 144.74 159.7 134.14 177.93 123.53 141.47 102.31"/>
</g>
<g>
<polygon class="cls-4" points="30.32 105.88 141.47 41.17 181.89 64.7 70.73 129.41 30.32 105.88"/>
<polyline class="cls-5" points="141.47 85.92 68.77 128.26 32.31 107.04 141.47 43.48"/>
<polygon class="cls-6" points="70.74 176.47 181.89 111.76 181.89 64.7 70.74 129.41 70.74 176.47"/>
<polygon class="cls-7" points="141.47 43.48 141.47 85.92 159.7 75.31 177.93 64.7 141.47 43.48"/>
</g>
<polygon class="cls-8" points="10.1 70.58 10.11 341.17 70.74 376.47 70.73 105.88 10.1 70.58"/>
<polygon class="cls-6" points="20.21 170.59 60.63 194.12 60.63 241.18 20.21 217.65 20.21 170.59"/>
<polygon class="cls-6" points="20.21 111.76 60.63 135.29 60.63 182.35 20.21 158.82 20.21 111.76"/>
<g>
<polygon class="cls-9" points="70.74 105.88 192 35.29 192 305.88 70.74 376.47 70.74 105.88"/>
<polygon class="cls-10" points="10.11 70.59 131.37 0 192 35.29 70.74 105.88 10.11 70.59"/>
</g>
<polygon class="cls-6" points="20.22 288.23 60.64 311.76 60.64 358.82 20.22 335.29 20.22 288.23"/>
<path class="cls-8" d="M46.75,333.53l-9.35-4.3,0,5.22-3.59-1.67.06-5.2-2.6-1.19.05-3,2.58,1.18L34,312.61l4.26,1.91,8.5,16.73Zm-3.82-4.83v-.06l-4.56-8.59a20.17,20.17,0,0,1-1-2.3l-.06,0a21.54,21.54,0,0,1,.12,2.17l0,6.28Z"/>
<g id="front-drawerr">
<polyline class="cls-2" points="60.63 311.76 40.42 323.53 0 300 20.21 288.23"/>
<polygon class="cls-11" points="0 300 40.42 323.53 40.42 370.59 0 347.06 0 300"/>
<polygon class="cls-1" points="40.42 370.59 60.63 358.82 60.63 311.76 40.42 323.53 40.42 370.59"/>
<polyline class="cls-1" points="58.65 310.61 40.42 321.22 3.97 300 22.19 289.39"/>
<polygon class="cls-3" points="22.19 289.39 22.19 310.61 40.42 321.22 58.65 310.61 22.19 289.39"/>
<path class="cls-12" d="M23.23,339.27v-12l-4.36-2.36L10.11,333v2.26l9.5,5.12v5.24l3.63,2v-5.24l2.63,1.43v-3.07ZM19.61,331v6.29l-5.6-3v-.06l4.69-4a5.54,5.54,0,0,0,1-1.32l.06,0A17.65,17.65,0,0,0,19.61,331Z"/>
</g>
<g>
<polygon class="cls-6" points="20.22 229.41 60.64 252.94 60.64 300 20.22 276.47 20.22 229.41"/>
<path class="cls-8" d="M33.74,265.64a16.52,16.52,0,0,0,4.6,4.93c2.12,1.32,3.66,1,3.66-.75,0-2.14-2-4.37-4.34-5.85l-1.4-.88-.82-2.4,3.71-2.09a7.93,7.93,0,0,1,1.46-.63v-.06s-.6-.29-1.8-1l-6-3.78v-3.17l12.38,7.72V260l-5,2.65c2.8,2.15,5.54,5.86,5.54,9.38s-2.64,5-7.16,2.2a22,22,0,0,1-6.72-6.91Z"/>
</g>
<path class="cls-8" d="M32.29,147.62l4.4,2.95,0-11.26c0-.69,0-1.36,0-1.36l-.06,0a1.72,1.72,0,0,1-.88.55l-1.63.45L32,135.19l5.09-1.49,3.22,2.15V153l4.4,3v3.17l-12.42-8.31Z"/>
<path class="cls-8" d="M32.61,210.69c0-7.57,9.51-3.1,9.51-7.41a5.94,5.94,0,0,0-3.07-4.79c-2.36-1.36-3.65.41-3.65.41l-2.73-3.49s1.87-3.15,6.7-.43c3.56,2,6.57,6.06,6.57,10.2,0,7-9.08,2.71-9.18,6.67l9.5,5.92v3.44l-13.48-8.55A16.93,16.93,0,0,1,32.61,210.69Z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,103 @@
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="376.47" viewBox="0 0 192 376.47">
<defs>
<style>
.cls-1 {
fill: #4b5fef;
}
.cls-2 {
fill: #7687ff;
}
.cls-3 {
fill: #1b2559;
}
.cls-12, .cls-4 {
fill: #fff;
}
.cls-4, .cls-7, .cls-9 {
opacity: 0.74;
}
.cls-5 {
fill: #b8c2e6;
}
.cls-6 {
fill: #d2d8ff;
}
.cls-11, .cls-7 {
fill: #5a67ff;
}
.cls-8 {
fill: #ebefff;
}
.cls-9 {
fill: #ccd8ff;
}
.cls-10 {
fill: #dee5fc;
opacity: 0.8;
}
</style>
</defs>
<g id="Layer_1" data-name="Layer 1">
<g>
<g>
<polyline class="cls-1" points="70.74 274.16 123.23 304.73 159.69 283.51 70.74 231.72"/>
<polygon class="cls-2" points="161.68 282.35 70.74 229.41 30.32 252.94 121.27 305.88 161.68 282.35"/>
<polygon class="cls-1" points="121.26 352.94 30.32 300 30.32 252.94 121.26 305.88 121.26 352.94"/>
<polygon class="cls-3" points="70.74 231.72 70.74 274.16 52.51 263.55 34.28 252.94 70.74 231.72"/>
</g>
<g>
<polygon class="cls-4" points="161.67 223.53 50.52 158.82 10.11 182.35 121.26 247.06 161.67 223.53"/>
<polyline class="cls-5" points="50.52 203.57 123.23 245.91 159.68 224.69 50.52 161.13"/>
<polygon class="cls-6" points="121.26 294.12 10.1 229.41 10.11 182.35 121.26 247.06 121.26 294.12"/>
<polygon class="cls-7" points="50.52 161.13 50.52 203.57 32.29 192.96 14.07 182.35 50.52 161.13"/>
</g>
<g>
<polygon class="cls-4" points="161.68 164.7 50.53 100 10.11 123.53 121.27 188.23 161.68 164.7"/>
<polyline class="cls-5" points="50.53 144.74 123.23 187.08 159.69 165.86 50.53 102.31"/>
<polygon class="cls-6" points="121.26 235.29 10.11 170.59 10.11 123.53 121.26 188.23 121.26 235.29"/>
<polygon class="cls-7" points="50.53 102.31 50.53 144.74 32.3 134.14 14.07 123.53 50.53 102.31"/>
</g>
<g>
<polygon class="cls-4" points="161.68 105.88 50.53 41.17 10.11 64.7 121.27 129.41 161.68 105.88"/>
<polyline class="cls-5" points="50.53 85.92 123.23 128.26 159.69 107.04 50.53 43.48"/>
<polygon class="cls-6" points="121.26 176.47 10.11 111.76 10.11 64.7 121.26 129.41 121.26 176.47"/>
<polygon class="cls-7" points="50.53 43.48 50.53 85.92 32.3 75.31 14.07 64.7 50.53 43.48"/>
</g>
<polygon class="cls-8" points="181.9 70.58 181.89 341.17 121.26 376.47 121.27 105.88 181.9 70.58"/>
<g>
<polygon class="cls-6" points="171.79 170.59 131.37 194.12 131.37 241.18 171.79 217.65 171.79 170.59"/>
<path class="cls-8" d="M145.58,219.91c0-7.37,9.32-13.54,9.29-17.56,0-1.77-1.37-2.11-3-1.22-2.31,1.23-3.56,4.31-3.56,4.31l-2.69-.41a15.23,15.23,0,0,1,6.54-7.55c3.47-1.83,6.45-1.24,6.49,2.56.07,6.46-8.86,12.39-8.93,16.28l9.37-5.08,0,3.15-13.34,7.25A13.31,13.31,0,0,1,145.58,219.91Z"/>
</g>
<polygon class="cls-6" points="171.79 111.76 131.37 135.29 131.37 182.35 171.79 158.82 171.79 111.76"/>
<path class="cls-8" d="M147.66,155.77l4.35-2.48L152,142c0-.69,0-1.39,0-1.39l0,0a10.31,10.31,0,0,1-.87,1.63l-1.59,2.45-2.12-1.11,5-7.68,3.14-1.76.12,17.07,4.35-2.48,0,3.15-12.29,7Z"/>
<g>
<polygon class="cls-9" points="121.26 105.88 0 35.29 0 305.88 121.26 376.47 121.26 105.88"/>
<polygon class="cls-10" points="181.89 70.59 60.63 0 0 35.29 121.26 105.88 181.89 70.59"/>
</g>
<polygon class="cls-6" points="171.78 288.23 131.36 311.76 131.36 358.82 171.78 335.29 171.78 288.23"/>
<path class="cls-8" d="M145.25,331.25l8.5-16.73,4.26-1.91.14,11.93,2.58-1.18.05,3-2.6,1.19.06,5.2-3.59,1.67-.05-5.22-9.35,4.3Zm9.33-5.07-.05-6.28a21.54,21.54,0,0,1,.12-2.17l-.06,0a20.17,20.17,0,0,1-1,2.3l-4.56,8.59v.06Z"/>
<g id="front-drawerr">
<polyline class="cls-2" points="131.37 311.76 151.58 323.53 192 300 171.79 288.23"/>
<polygon class="cls-11" points="192 300 151.58 323.53 151.58 370.59 192 347.06 192 300"/>
<polygon class="cls-1" points="151.58 370.59 131.37 358.82 131.37 311.76 151.58 323.53 151.58 370.59"/>
<polyline class="cls-1" points="133.35 310.61 151.58 321.22 188.03 300 169.81 289.39"/>
<polygon class="cls-3" points="169.81 289.39 169.81 310.61 151.58 321.22 133.35 310.61 169.81 289.39"/>
<path class="cls-12" d="M165.58,341.32l8.51-16.73,4.25-1.92.14,11.94,2.59-1.18,0,3-2.59,1.19.06,5.2-3.6,1.66,0-5.22-9.36,4.3Zm9.33-5.08,0-6.27a21.61,21.61,0,0,1,.12-2.18l-.06,0a18.3,18.3,0,0,1-1,2.3l-4.56,8.59v.05Z"/>
</g>
<g>
<polygon class="cls-6" points="171.78 229.41 131.36 252.94 131.36 300 171.78 276.47 171.78 229.41"/>
<path class="cls-8" d="M147.85,271.44a4.92,4.92,0,0,0,4.55-.35c2.08-1.11,3.6-3.2,3.58-4.94,0-2.14-2-2.11-4.29-.88l-1.38.73-.82-1.47,3.62-6.31c.77-1.35,1.41-2.29,1.41-2.29v-.05s-.59.39-1.76,1L146.85,260v-3.19l12.07-6.28,0,2.29-4.8,8.27c2.75-1.05,5.46-.48,5.5,3s-2.52,8-7,10.38c-4.27,2.29-6.65.83-6.65.83Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5 KiB

6
package-lock.json generated
View file

@ -14459,6 +14459,7 @@
"from": "git+https://github.com/lamassu/lamassu-coins.git",
"requires": {
"bech32": "2.0.0",
"big-integer": "^1.6.48",
"bignumber.js": "^9.0.0",
"bitcoinjs-lib": "4.0.3",
"bs58check": "^2.0.2",
@ -14474,6 +14475,11 @@
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
},
"big-integer": {
"version": "1.6.51",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
},
"bip32": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/bip32/-/bip32-1.0.4.tgz",

View file

@ -1,13 +1,20 @@
{
"files": {
"main.js": "/static/js/main.ff8544f4.chunk.js",
"main.js.map": "/static/js/main.ff8544f4.chunk.js.map",
"main.js": "/static/js/main.144ef1be.chunk.js",
"main.js.map": "/static/js/main.144ef1be.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.5b925903.js",
"runtime-main.js.map": "/static/js/runtime-main.5b925903.js.map",
"static/js/2.cd718274.chunk.js": "/static/js/2.cd718274.chunk.js",
"static/js/2.cd718274.chunk.js.map": "/static/js/2.cd718274.chunk.js.map",
"static/js/2.e38b81ec.chunk.js": "/static/js/2.e38b81ec.chunk.js",
"static/js/2.e38b81ec.chunk.js.map": "/static/js/2.e38b81ec.chunk.js.map",
"index.html": "/index.html",
"static/js/2.cd718274.chunk.js.LICENSE.txt": "/static/js/2.cd718274.chunk.js.LICENSE.txt",
"static/js/2.e38b81ec.chunk.js.LICENSE.txt": "/static/js/2.e38b81ec.chunk.js.LICENSE.txt",
"static/media/3-cassettes-open-1-left.d6d9aa73.svg": "/static/media/3-cassettes-open-1-left.d6d9aa73.svg",
"static/media/3-cassettes-open-2-left.a9ee8d4c.svg": "/static/media/3-cassettes-open-2-left.a9ee8d4c.svg",
"static/media/3-cassettes-open-3-left.08fed660.svg": "/static/media/3-cassettes-open-3-left.08fed660.svg",
"static/media/4-cassettes-open-1-left.7b00c51f.svg": "/static/media/4-cassettes-open-1-left.7b00c51f.svg",
"static/media/4-cassettes-open-2-left.b3d9541c.svg": "/static/media/4-cassettes-open-2-left.b3d9541c.svg",
"static/media/4-cassettes-open-3-left.e8f1667c.svg": "/static/media/4-cassettes-open-3-left.e8f1667c.svg",
"static/media/4-cassettes-open-4-left.bc1a9829.svg": "/static/media/4-cassettes-open-4-left.bc1a9829.svg",
"static/media/acceptor-left.f37bcb1a.svg": "/static/media/acceptor-left.f37bcb1a.svg",
"static/media/both-filled.7af80d5f.svg": "/static/media/both-filled.7af80d5f.svg",
"static/media/carousel-left-arrow.04e38344.svg": "/static/media/carousel-left-arrow.04e38344.svg",
@ -125,7 +132,7 @@
},
"entrypoints": [
"static/js/runtime-main.5b925903.js",
"static/js/2.cd718274.chunk.js",
"static/js/main.ff8544f4.chunk.js"
"static/js/2.e38b81ec.chunk.js",
"static/js/main.144ef1be.chunk.js"
]
}

View file

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="robots" content="noindex"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Lamassu Admin</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.cd718274.chunk.js"></script><script src="/static/js/main.ff8544f4.chunk.js"></script></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"/><meta name="robots" content="noindex"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Lamassu Admin</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="root"></div><script>!function(e){function r(r){for(var n,a,l=r[0],i=r[1],f=r[2],c=0,s=[];c<l.length;c++)a=l[c],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={1:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,function(r){return e[r]}.bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="/";var l=this["webpackJsonplamassu-admin"]=this["webpackJsonplamassu-admin"]||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var f=0;f<l.length;f++)r(l[f]);var p=i;t()}([])</script><script src="/static/js/2.e38b81ec.chunk.js"></script><script src="/static/js/main.144ef1be.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more