Feat: make dashboard and machine profile page

This commit is contained in:
Cesar 2020-11-17 12:13:10 +00:00 committed by Josh Harvey
parent 1df90c3196
commit c21cdb13e1
29 changed files with 717 additions and 135 deletions

View file

@ -240,6 +240,10 @@ const typeDefs = gql`
created: Date
age: Float
deviceTime: Date
type Rate {
code: String
name: String
rate: Float
}
type Rate {

View file

@ -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)
}

View file

@ -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 }

View file

@ -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
}

View file

@ -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>
</>
</>
)}
</>
)
}

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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 => {

View file

@ -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 }) => {

View file

@ -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

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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'

View file

@ -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

View file

@ -8,7 +8,6 @@ import {
CellMeasurer,
CellMeasurerCache
} from 'react-virtualized'
import {
Table,
TBody,

View file

@ -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'
})
}

View file

@ -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)

View file

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

View file

@ -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

View file

@ -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'

View file

@ -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 }

View file

@ -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'

View file

@ -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',