Feat: make dashboard and machine profile page
This commit is contained in:
parent
1df90c3196
commit
c21cdb13e1
29 changed files with 717 additions and 135 deletions
|
|
@ -240,6 +240,10 @@ const typeDefs = gql`
|
|||
created: Date
|
||||
age: Float
|
||||
deviceTime: Date
|
||||
type Rate {
|
||||
code: String
|
||||
name: String
|
||||
rate: Float
|
||||
}
|
||||
|
||||
type Rate {
|
||||
|
|
|
|||
|
|
@ -147,21 +147,6 @@ function plugins (settings, deviceId) {
|
|||
]
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
function getLcmOrBigx2 (n1, n2) {
|
||||
let big = Math.max(n1, n2)
|
||||
let small = Math.min(n1, n2)
|
||||
|
||||
let i = big * 2
|
||||
while (i % small !== 0) {
|
||||
i += lar
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
|
||||
function buildAvailableCassettes (excludeTxId) {
|
||||
const cashOutConfig = configManager.getCashOut(deviceId, settings.config)
|
||||
|
||||
|
|
@ -169,7 +154,6 @@ function plugins (settings, deviceId) {
|
|||
|
||||
const denominations = [cashOutConfig.top, cashOutConfig.bottom]
|
||||
|
||||
<<<<<<< HEAD
|
||||
const virtualCassettes = [Math.max(cashOutConfig.top, cashOutConfig.bottom) * 2]
|
||||
|
||||
return Promise.all([dbm.cassetteCounts(deviceId), cashOutHelper.redeemableTxs(deviceId, excludeTxId)])
|
||||
|
|
@ -179,11 +163,6 @@ function plugins (settings, deviceId) {
|
|||
const counts = argv.cassettes
|
||||
? argv.cassettes.split(',')
|
||||
: rec.counts
|
||||
=======
|
||||
const virtualCassettes = [
|
||||
getLcmOrBigx2(cashOutConfig.top, cashOutConfig.bottom)
|
||||
]
|
||||
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
|
||||
|
||||
return Promise.all([
|
||||
dbm.cassetteCounts(deviceId),
|
||||
|
|
@ -288,7 +267,6 @@ function plugins (settings, deviceId) {
|
|||
currentConfigVersionPromise
|
||||
].concat(tickerPromises, balancePromises, testnetPromises, currentAvailablePromoCodes)
|
||||
|
||||
<<<<<<< HEAD
|
||||
return Promise.all(promises)
|
||||
.then(arr => {
|
||||
const cassettes = arr[0]
|
||||
|
|
@ -310,26 +288,6 @@ function plugins (settings, deviceId) {
|
|||
areThereAvailablePromoCodes
|
||||
}
|
||||
})
|
||||
=======
|
||||
return Promise.all(promises).then(arr => {
|
||||
const cassettes = arr[0]
|
||||
const configVersion = arr[2]
|
||||
const cryptoCodesCount = cryptoCodes.length
|
||||
const tickers = arr.slice(3, cryptoCodesCount + 3)
|
||||
const balances = arr.slice(cryptoCodesCount + 3, 2 * cryptoCodesCount + 3)
|
||||
const testNets = arr.slice(2 * cryptoCodesCount + 3)
|
||||
const coinParams = _.zip(cryptoCodes, testNets)
|
||||
const coinsWithoutRate = _.map(mapCoinSettings, coinParams)
|
||||
|
||||
return {
|
||||
cassettes,
|
||||
rates: buildRates(tickers),
|
||||
balances: buildBalances(balances),
|
||||
coins: _.zipWith(_.assign, coinsWithoutRate, tickers),
|
||||
configVersion
|
||||
}
|
||||
})
|
||||
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
|
||||
}
|
||||
|
||||
function sendCoins (tx) {
|
||||
|
|
@ -648,15 +606,8 @@ function plugins (settings, deviceId) {
|
|||
const notifications = configManager.getGlobalNotifications(settings.config)
|
||||
|
||||
let promises = []
|
||||
<<<<<<< HEAD
|
||||
if (notifications.email.active && rec.email) promises.push(email.sendMessage(settings, rec))
|
||||
if (notifications.sms.active && rec.sms) promises.push(sms.sendMessage(settings, rec))
|
||||
=======
|
||||
if (notifications.email.active && rec.email)
|
||||
promises.push(email.sendMessage(settings, rec))
|
||||
if (notifications.sms.active && rec.sms)
|
||||
promises.push(sms.sendMessage(settings, rec))
|
||||
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
|
||||
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
|
@ -757,25 +708,11 @@ function plugins (settings, deviceId) {
|
|||
fiatCode
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (_.isFinite(lowAlertThreshold) && BN(fiatBalance.balance).lt(lowAlertThreshold)) {
|
||||
=======
|
||||
if (
|
||||
_.isFinite(lowAlertThreshold) &&
|
||||
BN(fiatBalance.balance).lt(lowAlertThreshold)
|
||||
)
|
||||
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
|
||||
return _.set('code')('LOW_CRYPTO_BALANCE')(req)
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
if (_.isFinite(highAlertThreshold) && BN(fiatBalance.balance).gt(highAlertThreshold)) {
|
||||
=======
|
||||
if (
|
||||
_.isFinite(highAlertThreshold) &&
|
||||
BN(fiatBalance.balance).gt(highAlertThreshold)
|
||||
)
|
||||
>>>>>>> 1706b2c... Feat: save highVolumeTxs on DB and plugins code refactor
|
||||
return _.set('code')('HIGH_CRYPTO_BALANCE')(req)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ const mem = require('mem')
|
|||
const configManager = require('./new-config-manager')
|
||||
const ph = require('./plugin-helper')
|
||||
const logger = require('./logger')
|
||||
const axios = require('axios')
|
||||
|
||||
const lastRate = {}
|
||||
|
||||
|
|
@ -39,4 +40,26 @@ const getRates = mem(_getRates, {
|
|||
cacheKey: (settings, fiatCode, cryptoCode) => JSON.stringify([fiatCode, cryptoCode])
|
||||
})
|
||||
|
||||
module.exports = { getRates }
|
||||
const getBtcRates = (to = null, from = 'USD') => {
|
||||
// if to !== null, then we return only the rates with from (default USD) and to (so an array with 2 items)
|
||||
return axios.get('https://bitpay.com/api/rates').then(response => {
|
||||
const fxRates = response.data
|
||||
if (to === null) {
|
||||
return fxRates
|
||||
}
|
||||
const toRate = fxRates.find(o => o.code === to)
|
||||
const fromRate = fxRates.find(o => o.code === from)
|
||||
|
||||
let res = []
|
||||
if (toRate && to !== from) {
|
||||
res = [...res, toRate]
|
||||
}
|
||||
if (fromRate) {
|
||||
res = [...res, fromRate]
|
||||
}
|
||||
|
||||
return res
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { getBtcRates, getRates }
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ const TL2 = pBuilder('tl2')
|
|||
const Label1 = pBuilder('label1')
|
||||
const Label2 = pBuilder('label2')
|
||||
const Label3 = pBuilder('label3')
|
||||
const Label4 = pBuilder('regularLabel')
|
||||
|
||||
function pBuilder(elementClass) {
|
||||
return ({ inline, noMargin, className, children, ...props }) => {
|
||||
|
|
@ -124,5 +125,6 @@ export {
|
|||
Mono,
|
||||
Label1,
|
||||
Label2,
|
||||
Label3
|
||||
Label3,
|
||||
Label4
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,76 +1,130 @@
|
|||
import { useQuery } from '@apollo/react-hooks'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import React from 'react'
|
||||
|
||||
import { cardState as cardState_ } from 'src/components/CollapsibleCard'
|
||||
import gql from 'graphql-tag'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Label1, H4 } from 'src/components/typography'
|
||||
|
||||
import styles from './Alerts.styles'
|
||||
import styles from '../Dashboard.styles'
|
||||
|
||||
import AlertsTable from './AlertsTable'
|
||||
|
||||
const NUM_TO_RENDER = 3
|
||||
|
||||
const data = {
|
||||
alerts: [
|
||||
{ text: 'alert 1' },
|
||||
{ text: 'alert 2' },
|
||||
{ text: 'alert 3' },
|
||||
{ text: 'alert 4' },
|
||||
{ text: 'alert 5' }
|
||||
]
|
||||
}
|
||||
const GET_ALERTS = gql`
|
||||
query getAlerts {
|
||||
alerts {
|
||||
id
|
||||
type
|
||||
detail
|
||||
message
|
||||
created
|
||||
read
|
||||
valid
|
||||
}
|
||||
machines {
|
||||
deviceId
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const Alerts = ({ onReset, onExpand, size }) => {
|
||||
const Alerts = ({ cardState, setRightSideState }) => {
|
||||
const classes = useStyles()
|
||||
const [showAllItems, setShowAllItems] = useState(false)
|
||||
const { data } = useQuery(GET_ALERTS)
|
||||
|
||||
const showAllItems = size === cardState_.EXPANDED
|
||||
const alerts = R.path(['alerts'])(data) ?? []
|
||||
const machines = R.compose(
|
||||
R.map(R.prop('name')),
|
||||
R.indexBy(R.prop('deviceId'))
|
||||
)(data?.machines ?? [])
|
||||
|
||||
const alertsLength = () => (data ? data.alerts.length : 0)
|
||||
const showExpandButton = alerts.length > NUM_TO_RENDER && !showAllItems
|
||||
|
||||
useEffect(() => {
|
||||
if (cardState.cardSize === 'small' || cardState.cardSize === 'default') {
|
||||
setShowAllItems(false)
|
||||
}
|
||||
}, [cardState.cardSize])
|
||||
|
||||
const reset = () => {
|
||||
setRightSideState({
|
||||
systemStatus: { cardSize: 'default', buttonName: 'Show less' },
|
||||
alerts: { cardSize: 'default', buttonName: 'Show less' }
|
||||
})
|
||||
setShowAllItems(false)
|
||||
}
|
||||
|
||||
const showAllClick = () => {
|
||||
setShowAllItems(true)
|
||||
setRightSideState({
|
||||
systemStatus: { cardSize: 'small', buttonName: 'Show machines' },
|
||||
alerts: { cardSize: 'big', buttonName: 'Show less' }
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classes.container}>
|
||||
<H4 className={classes.h4}>{`Alerts (${alertsLength()})`}</H4>
|
||||
{showAllItems && (
|
||||
<Label1 className={classes.upperButtonLabel}>
|
||||
<Button
|
||||
onClick={onReset}
|
||||
size="small"
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
className={classes.button}>
|
||||
{'Show less'}
|
||||
</Button>
|
||||
</Label1>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<H4 className={classes.h4}>{`Alerts ${
|
||||
data ? `(${alerts.length})` : 0
|
||||
}`}</H4>
|
||||
{(showAllItems || cardState.cardSize === 'small') && (
|
||||
<>
|
||||
<Label1
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
marginBottom: 0,
|
||||
marginTop: 0
|
||||
}}>
|
||||
<Button
|
||||
onClick={reset}
|
||||
size="small"
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
className={classes.button}>
|
||||
{cardState.buttonName}
|
||||
</Button>
|
||||
</Label1>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<AlertsTable
|
||||
numToRender={showAllItems ? data?.alerts.length : NUM_TO_RENDER}
|
||||
alerts={data?.alerts ?? []}
|
||||
/>
|
||||
{!showAllItems && (
|
||||
<>
|
||||
<Label1 className={classes.centerLabel}>
|
||||
<Button
|
||||
onClick={() => onExpand('alerts')}
|
||||
size="small"
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
className={classes.button}>
|
||||
{`Show all (${data.alerts.length})`}
|
||||
</Button>
|
||||
</Label1>
|
||||
</>
|
||||
)}
|
||||
{cardState.cardSize !== 'small' && (
|
||||
<>
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<AlertsTable
|
||||
numToRender={showAllItems ? alerts.length : NUM_TO_RENDER}
|
||||
alerts={alerts}
|
||||
machines={machines}
|
||||
/>
|
||||
{showExpandButton && (
|
||||
<>
|
||||
<Label1 style={{ textAlign: 'center', marginBottom: 0 }}>
|
||||
<Button
|
||||
onClick={showAllClick}
|
||||
size="small"
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
className={classes.button}>
|
||||
{`Show all (${alerts.length})`}
|
||||
</Button>
|
||||
</Label1>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import Grid from '@material-ui/core/Grid'
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import classnames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import classnames from 'classnames'
|
|||
import gql from 'graphql-tag'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import { Label2 } from 'src/components/typography'
|
||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import Button from '@material-ui/core/Button'
|
|||
import Grid from '@material-ui/core/Grid'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import CollapsibleCard, { cardState } from 'src/components/CollapsibleCard'
|
||||
import { H4, Label1 } from 'src/components/typography'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import * as d3 from 'd3'
|
||||
import * as R from 'ramda'
|
||||
import React, { useEffect, useRef, useCallback } from 'react'
|
||||
|
||||
import { backgroundColor, zircon, primaryColor } from 'src/styling/variables'
|
||||
|
||||
const transactionProfit = tx => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import * as d3 from 'd3'
|
|||
import moment from 'moment'
|
||||
import * as R from 'ramda'
|
||||
import React, { useEffect, useRef, useCallback } from 'react'
|
||||
|
||||
import { backgroundColor, java, neon } from 'src/styling/variables'
|
||||
|
||||
const RefScatterplot = ({ data: realData, timeFrame }) => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
/*eslint-disable*/
|
||||
import { scaleLinear, scaleTime, max, axisLeft, axisBottom, select } from 'd3'
|
||||
import React, { useMemo } from 'react'
|
||||
import moment from 'moment'
|
||||
|
||||
const data = [
|
||||
[0, '2020-11-08T18:00:05.664Z'],
|
||||
[40.01301, '2020-11-09T11:17:05.664Z']
|
||||
]
|
||||
|
||||
const marginTop = 10
|
||||
const marginRight = 30
|
||||
const marginBottom = 30
|
||||
const marginLeft = 60
|
||||
const width = 510 - marginLeft - marginRight
|
||||
const height = 141 - marginTop - marginBottom
|
||||
|
||||
const Scatterplot = ({ data: realData }) => {
|
||||
const x = scaleTime()
|
||||
.domain([
|
||||
moment()
|
||||
.add(-1, 'day')
|
||||
.valueOf(),
|
||||
moment().valueOf()
|
||||
])
|
||||
.range([0, width])
|
||||
.nice()
|
||||
|
||||
const y = scaleLinear()
|
||||
.domain([0, 1000])
|
||||
.range([height, 0])
|
||||
.nice()
|
||||
|
||||
// viewBox="0 0 540 141"
|
||||
return (
|
||||
<>
|
||||
<svg
|
||||
width={width + marginLeft + marginRight}
|
||||
height={height + marginTop + marginBottom}>
|
||||
<g transform={`translate(${marginLeft},${marginTop})`}>
|
||||
<XAxis
|
||||
transform={`translate(0, ${height + marginTop})`}
|
||||
scale={x}
|
||||
numTicks={6}
|
||||
/>
|
||||
<g>{axisLeft(y)}</g>
|
||||
{/* <YAxis transform={`translate(0, 0)`} scale={y} numTicks={6} /> */}
|
||||
<RenderCircles data={data} scale={{ x, y }} />
|
||||
</g>
|
||||
</svg>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const XAxis = ({
|
||||
range = [10, 500],
|
||||
transform,
|
||||
scale: xScale,
|
||||
numTicks = 7
|
||||
}) => {
|
||||
const ticks = useMemo(() => {
|
||||
return xScale.ticks(numTicks).map(value => ({
|
||||
value,
|
||||
xOffset: xScale(value)
|
||||
}))
|
||||
}, [range.join('-')])
|
||||
|
||||
return (
|
||||
<g transform={transform}>
|
||||
{ticks.map(({ value, xOffset }) => (
|
||||
<g key={value} transform={`translate(${xOffset}, 0)`}>
|
||||
<text
|
||||
key={value}
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
textAnchor: 'middle',
|
||||
transform: 'translateY(10px)'
|
||||
}}>
|
||||
{value.getHours()}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
const YAxis = ({
|
||||
range = [10, 500],
|
||||
transform,
|
||||
scale: xScale,
|
||||
numTicks = 7
|
||||
}) => {
|
||||
const ticks = useMemo(() => {
|
||||
return xScale.ticks(numTicks).map(value => ({
|
||||
value,
|
||||
xOffset: xScale(value)
|
||||
}))
|
||||
}, [range.join('-')])
|
||||
|
||||
return (
|
||||
<g transform={transform}>
|
||||
{ticks.map(({ value, xOffset }) => (
|
||||
<g key={value} transform={`translate(0, ${xOffset})`}>
|
||||
<text
|
||||
key={value}
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
textAnchor: 'middle',
|
||||
transform: 'translateX(-10px)'
|
||||
}}>
|
||||
{value}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
const RenderCircles = ({ data, scale }) => {
|
||||
let renderCircles = data.map((item, idx) => {
|
||||
return (
|
||||
<circle
|
||||
cx={scale.x(new Date(item[1]))}
|
||||
cy={scale.y(item[0])}
|
||||
r="4"
|
||||
style={{ fill: 'rgba(25, 158, 199, .9)' }}
|
||||
key={idx}
|
||||
/>
|
||||
)
|
||||
})
|
||||
return <g>{renderCircles}</g>
|
||||
}
|
||||
|
||||
export default Scatterplot
|
||||
|
|
@ -2,7 +2,6 @@ import { makeStyles } from '@material-ui/core/styles'
|
|||
import classnames from 'classnames'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import { H4 } from 'src/components/typography'
|
||||
|
||||
import styles from './SystemPerformance.styles'
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import gql from 'graphql-tag'
|
|||
import moment from 'moment'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import { Label2 } from 'src/components/typography/index'
|
||||
import { ReactComponent as TriangleDown } from 'src/styling/icons/arrow/triangle_down.svg'
|
||||
import { ReactComponent as TriangleUp } from 'src/styling/icons/arrow/triangle_up.svg'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import TableRow from '@material-ui/core/TableRow'
|
|||
import classnames from 'classnames'
|
||||
import React from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import { Status } from 'src/components/Status'
|
||||
import { Label2, TL2 } from 'src/components/typography'
|
||||
// import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import Grid from '@material-ui/core/Grid'
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import gql from 'graphql-tag'
|
||||
import React from 'react'
|
||||
|
||||
import { cardState as cardState_ } from 'src/components/CollapsibleCard'
|
||||
// import ActionButton from 'src/components/buttons/ActionButton'
|
||||
import { H4, TL2, Label1 } from 'src/components/typography'
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@ import { useMutation } from '@apollo/react-hooks'
|
|||
import { makeStyles } from '@material-ui/core'
|
||||
import gql from 'graphql-tag'
|
||||
import React from 'react'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import { Table as EditableTable } from 'src/components/editableTable'
|
||||
import { CashOut } from 'src/components/inputs/cashbox/Cashbox'
|
||||
import { NumberInput } from 'src/components/inputs/formik'
|
||||
import { fromNamespace } from 'src/utils/config'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import styles from './Cassettes.styles'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { useQuery, useMutation } from '@apollo/react-hooks'
|
|||
import gql from 'graphql-tag'
|
||||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
|
||||
import { Table as EditableTable } from 'src/components/editableTable'
|
||||
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import moment from 'moment'
|
||||
import React from 'react'
|
||||
|
||||
import { Label3, P } from 'src/components/typography'
|
||||
|
||||
import styles from '../Machines.styles'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import classnames from 'classnames'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { CopyToClipboard as ReactCopyToClipboard } from 'react-copy-to-clipboard'
|
||||
|
||||
import Popover from 'src/components/Popper'
|
||||
import { ReactComponent as CopyIcon } from 'src/styling/icons/action/copy/copy.svg'
|
||||
import { comet } from 'src/styling/variables'
|
||||
|
||||
import { cpcStyles } from './Transactions.styles'
|
||||
|
||||
const useStyles = makeStyles(cpcStyles)
|
||||
|
||||
const CopyToClipboard = ({
|
||||
className,
|
||||
buttonClassname,
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (anchorEl) setTimeout(() => setAnchorEl(null), 3000)
|
||||
}, [anchorEl])
|
||||
|
||||
const classes = useStyles()
|
||||
|
||||
const handleClick = event => {
|
||||
setAnchorEl(anchorEl ? null : event.currentTarget)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const open = Boolean(anchorEl)
|
||||
const id = open ? 'simple-popper' : undefined
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
{children && (
|
||||
<>
|
||||
<div className={classnames(classes.address, className)}>
|
||||
{children}
|
||||
</div>
|
||||
<div className={classnames(classes.buttonWrapper, buttonClassname)}>
|
||||
<ReactCopyToClipboard text={R.replace(/\s/g, '')(children)}>
|
||||
<button
|
||||
aria-describedby={id}
|
||||
onClick={event => handleClick(event)}>
|
||||
<CopyIcon />
|
||||
</button>
|
||||
</ReactCopyToClipboard>
|
||||
</div>
|
||||
<Popover
|
||||
id={id}
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClose}
|
||||
arrowSize={3}
|
||||
bgColor={comet}
|
||||
placement="top">
|
||||
<div className={classes.popoverContent}>
|
||||
<div>Copied to clipboard!</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CopyToClipboard
|
||||
|
|
@ -8,7 +8,6 @@ import {
|
|||
CellMeasurer,
|
||||
CellMeasurerCache
|
||||
} from 'react-virtualized'
|
||||
|
||||
import {
|
||||
Table,
|
||||
TBody,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import { zircon } from 'src/styling/variables'
|
||||
|
||||
export default {
|
||||
expandButton: {
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
cursor: 'pointer',
|
||||
padding: 4
|
||||
},
|
||||
rowWrapper: {
|
||||
// workaround to shadows cut by r-virtualized when scroll is visible
|
||||
padding: 1
|
||||
},
|
||||
row: {
|
||||
border: [[2, 'solid', 'transparent']],
|
||||
borderRadius: 0
|
||||
},
|
||||
expanded: {
|
||||
border: [[2, 'solid', zircon]],
|
||||
boxShadow: '0 0 8px 0 rgba(0,0,0,0.08)'
|
||||
},
|
||||
before: {
|
||||
paddingTop: 12
|
||||
},
|
||||
after: {
|
||||
paddingBottom: 12
|
||||
},
|
||||
pointer: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
body: {
|
||||
flex: [[1, 1, 'auto']]
|
||||
},
|
||||
table: ({ width }) => ({
|
||||
marginBottom: 30,
|
||||
minHeight: 200,
|
||||
width,
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
import { makeStyles, Box } from '@material-ui/core'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import moment from 'moment'
|
||||
import React, { memo } from 'react'
|
||||
|
||||
import { IDButton } from 'src/components/buttons'
|
||||
import { Label1 } from 'src/components/typography'
|
||||
import { ReactComponent as CardIdInverseIcon } from 'src/styling/icons/ID/card/white.svg'
|
||||
import { ReactComponent as CardIdIcon } from 'src/styling/icons/ID/card/zodiac.svg'
|
||||
import { ReactComponent as PhoneIdInverseIcon } from 'src/styling/icons/ID/phone/white.svg'
|
||||
import { ReactComponent as PhoneIdIcon } from 'src/styling/icons/ID/phone/zodiac.svg'
|
||||
import { ReactComponent as CamIdInverseIcon } from 'src/styling/icons/ID/photo/white.svg'
|
||||
import { ReactComponent as CamIdIcon } from 'src/styling/icons/ID/photo/zodiac.svg'
|
||||
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
|
||||
import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-out.svg'
|
||||
import { URI } from 'src/utils/apollo'
|
||||
import { toUnit, formatCryptoAddress } from 'src/utils/coin'
|
||||
import { onlyFirstToUpper } from 'src/utils/string'
|
||||
|
||||
import CopyToClipboard from './CopyToClipboard'
|
||||
import styles from './DetailsCard.styles'
|
||||
import { getStatus } from './helper'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const formatAddress = (cryptoCode = '', address = '') =>
|
||||
formatCryptoAddress(cryptoCode, address).replace(/(.{5})/g, '$1 ')
|
||||
|
||||
const Label = ({ children }) => {
|
||||
const classes = useStyles()
|
||||
return <Label1 className={classes.label}>{children}</Label1>
|
||||
}
|
||||
|
||||
const DetailsRow = ({ it: tx }) => {
|
||||
const classes = useStyles()
|
||||
|
||||
const fiat = Number.parseFloat(tx.fiat)
|
||||
const crypto = toUnit(new BigNumber(tx.cryptoAtoms), tx.cryptoCode)
|
||||
const commissionPercentage = Number.parseFloat(tx.commissionPercentage, 2)
|
||||
const commission = Number(fiat * commissionPercentage).toFixed(2)
|
||||
const exchangeRate = Number(fiat / crypto).toFixed(3)
|
||||
const displayExRate = `1 ${tx.cryptoCode} = ${exchangeRate} ${tx.fiatCode}`
|
||||
|
||||
const customer = tx.customerIdCardData && {
|
||||
name: `${onlyFirstToUpper(
|
||||
tx.customerIdCardData.firstName
|
||||
)} ${onlyFirstToUpper(tx.customerIdCardData.lastName)}`,
|
||||
age: moment().diff(moment(tx.customerIdCardData.dateOfBirth), 'years'),
|
||||
country: tx.customerIdCardData.country,
|
||||
idCardNumber: tx.customerIdCardData.documentNumber,
|
||||
idCardExpirationDate: moment(tx.customerIdCardData.expirationDate).format(
|
||||
'DD-MM-YYYY'
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classes.row}>
|
||||
<div className={classes.direction}>
|
||||
<Label>Direction</Label>
|
||||
<div>
|
||||
<span className={classes.txIcon}>
|
||||
{tx.txClass === 'cashOut' ? <TxOutIcon /> : <TxInIcon />}
|
||||
</span>
|
||||
<span>{tx.txClass === 'cashOut' ? 'Cash-out' : 'Cash-in'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classes.availableIds}>
|
||||
<Label>Available IDs</Label>
|
||||
<Box display="flex" flexDirection="row">
|
||||
{tx.customerPhone && (
|
||||
<IDButton
|
||||
className={classes.idButton}
|
||||
name="phone"
|
||||
Icon={PhoneIdIcon}
|
||||
InverseIcon={PhoneIdInverseIcon}>
|
||||
{tx.customerPhone}
|
||||
</IDButton>
|
||||
)}
|
||||
{tx.customerIdCardPhotoPath && !tx.customerIdCardData && (
|
||||
<IDButton
|
||||
popoverClassname={classes.popover}
|
||||
className={classes.idButton}
|
||||
name="card"
|
||||
Icon={CardIdIcon}
|
||||
InverseIcon={CardIdInverseIcon}>
|
||||
<img
|
||||
className={classes.idCardPhoto}
|
||||
src={`${URI}/id-card-photo/${tx.customerIdCardPhotoPath}`}
|
||||
alt=""
|
||||
/>
|
||||
</IDButton>
|
||||
)}
|
||||
{tx.customerIdCardData && (
|
||||
<IDButton
|
||||
className={classes.idButton}
|
||||
name="card"
|
||||
Icon={CardIdIcon}
|
||||
InverseIcon={CardIdInverseIcon}>
|
||||
<div className={classes.idCardDataCard}>
|
||||
<div>
|
||||
<div>
|
||||
<Label>Name</Label>
|
||||
<div>{customer.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Age</Label>
|
||||
<div>{customer.age}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Country</Label>
|
||||
<div>{customer.country}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<Label>ID number</Label>
|
||||
<div>{customer.idCardNumber}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Expiration date</Label>
|
||||
<div>{customer.idCardExpirationDate}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</IDButton>
|
||||
)}
|
||||
{tx.customerFrontCameraPath && (
|
||||
<IDButton
|
||||
name="cam"
|
||||
Icon={CamIdIcon}
|
||||
InverseIcon={CamIdInverseIcon}>
|
||||
<img
|
||||
src={`${URI}/front-camera-photo/${tx.customerFrontCameraPath}`}
|
||||
alt=""
|
||||
/>
|
||||
</IDButton>
|
||||
)}
|
||||
</Box>
|
||||
</div>
|
||||
<div className={classes.exchangeRate}>
|
||||
<Label>Exchange rate</Label>
|
||||
<div>{crypto > 0 ? displayExRate : '-'}</div>
|
||||
</div>
|
||||
<div className={classes.commission}>
|
||||
<Label>Commission</Label>
|
||||
<div>
|
||||
{`${commission} ${tx.fiatCode} (${commissionPercentage * 100} %)`}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Label>Fixed fee</Label>
|
||||
<div>
|
||||
{tx.txClass === 'cashIn'
|
||||
? `${Number.parseFloat(tx.cashInFee)} ${tx.fiatCode}`
|
||||
: 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.secondRow}>
|
||||
<div className={classes.address}>
|
||||
<Label>Address</Label>
|
||||
<div>
|
||||
<CopyToClipboard>
|
||||
{formatAddress(tx.cryptoCode, tx.toAddress)}
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.transactionId}>
|
||||
<Label>Transaction ID</Label>
|
||||
<div>
|
||||
{tx.txClass === 'cashOut' ? (
|
||||
'N/A'
|
||||
) : (
|
||||
<CopyToClipboard>{tx.txHash}</CopyToClipboard>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.sessionId}>
|
||||
<Label>Session ID</Label>
|
||||
<CopyToClipboard>{tx.id}</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.lastRow}>
|
||||
<div>
|
||||
<Label>Transaction status</Label>
|
||||
<span className={classes.bold}>{getStatus(tx)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(DetailsRow, (prev, next) => prev.id === next.id)
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
import typographyStyles from 'src/components/typography/styles'
|
||||
import { offColor } from 'src/styling/variables'
|
||||
|
||||
const { p } = typographyStyles
|
||||
|
||||
export default {
|
||||
wrapper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
marginTop: 24
|
||||
},
|
||||
row: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginBottom: 36
|
||||
},
|
||||
secondRow: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 36
|
||||
},
|
||||
lastRow: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
marginBottom: 32
|
||||
},
|
||||
label: {
|
||||
color: offColor,
|
||||
margin: [[0, 0, 6, 0]]
|
||||
},
|
||||
txIcon: {
|
||||
marginRight: 10
|
||||
},
|
||||
popover: {
|
||||
height: 164,
|
||||
width: 215
|
||||
},
|
||||
idButton: {
|
||||
marginRight: 4
|
||||
},
|
||||
idCardDataCard: {
|
||||
extend: p,
|
||||
display: 'flex',
|
||||
padding: [[11, 8]],
|
||||
// rework this into a proper component
|
||||
'& > div': {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
'& > div': {
|
||||
width: 144,
|
||||
height: 37,
|
||||
marginBottom: 15,
|
||||
'&:last-child': {
|
||||
marginBottom: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
bold: {
|
||||
fontWeight: 700
|
||||
},
|
||||
direction: {
|
||||
width: 233
|
||||
},
|
||||
availableIds: {
|
||||
width: 232
|
||||
},
|
||||
exchangeRate: {
|
||||
width: 250
|
||||
},
|
||||
commission: {
|
||||
width: 217
|
||||
},
|
||||
address: {
|
||||
width: 280
|
||||
},
|
||||
transactionId: {
|
||||
width: 280
|
||||
},
|
||||
sessionId: {
|
||||
width: 215
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react'
|
||||
|
||||
import { Td } from 'src/components/fake-table/Table'
|
||||
import { ReactComponent as StripesSvg } from 'src/styling/icons/stripes.svg'
|
||||
|
||||
const Stripes = ({ width }) => (
|
||||
<Td width={width}>
|
||||
<StripesSvg />
|
||||
</Td>
|
||||
)
|
||||
|
||||
export default Stripes
|
||||
|
|
@ -5,7 +5,6 @@ import gql from 'graphql-tag'
|
|||
import moment from 'moment'
|
||||
import * as R from 'ramda'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import DetailsRow from 'src/pages/Transactions/DetailsCard'
|
||||
import { mainStyles } from 'src/pages/Transactions/Transactions.styles'
|
||||
import { getStatus } from 'src/pages/Transactions/helper'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
const getCashOutStatus = it => {
|
||||
if (it.hasError) return 'Error'
|
||||
if (it.dispense) return 'Success'
|
||||
if (it.expired) return 'Expired'
|
||||
return 'Pending'
|
||||
}
|
||||
|
||||
const getCashInStatus = it => {
|
||||
if (it.operatorCompleted) return 'Cancelled'
|
||||
if (it.hasError) return 'Error'
|
||||
if (it.sendConfirmed) return 'Sent'
|
||||
if (it.expired) return 'Expired'
|
||||
return 'Pending'
|
||||
}
|
||||
|
||||
const getStatus = it => {
|
||||
if (it.class === 'cashOut') {
|
||||
return getCashOutStatus(it)
|
||||
}
|
||||
return getCashInStatus(it)
|
||||
}
|
||||
|
||||
export { getStatus }
|
||||
|
|
@ -8,7 +8,6 @@ import gql from 'graphql-tag'
|
|||
import * as R from 'ramda'
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
|
||||
import { TL1, TL2, Label3 } from 'src/components/typography'
|
||||
|
||||
import Cassettes from './MachineComponents/Cassettes'
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@ const useStyles = makeStyles({
|
|||
})
|
||||
|
||||
const tree = [
|
||||
{
|
||||
key: 'dashboard',
|
||||
label: 'Dashboard',
|
||||
route: '/dashboard',
|
||||
component: Dashboard
|
||||
},
|
||||
{
|
||||
key: 'machines',
|
||||
label: 'Machines',
|
||||
route: '/machines',
|
||||
component: Machines
|
||||
},
|
||||
{
|
||||
key: 'transactions',
|
||||
label: 'Transactions',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue