Merge branch 'dev' into f56-billl-denominations

This commit is contained in:
Nikola Ubavić 2022-03-10 20:33:51 +01:00 committed by GitHub
commit 40ddd9e753
14 changed files with 111 additions and 112 deletions

View file

@ -587,7 +587,7 @@ function formatSubscriberInfo(customer) {
const subscriberInfo = customer.subscriberInfo const subscriberInfo = customer.subscriberInfo
if(!subscriberInfo) return customer if(!subscriberInfo) return customer
const result = subscriberInfo.result const result = subscriberInfo.result
if(subscriberInfo.status !== 'successful' || _.isEmpty(result)) return customer if(_.isEmpty(result)) return _.omit(['subscriberInfo'], customer)
const name = _.get('belongs_to.name')(result) const name = _.get('belongs_to.name')(result)
const street = _.get('current_addresses[0].street_line_1')(result) const street = _.get('current_addresses[0].street_line_1')(result)

View file

@ -49,7 +49,7 @@ const ALL_ACCOUNTS = [
{ code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER, dev: true }, { code: 'mock-id-verify', display: 'Mock ID verifier', class: ID_VERIFIER, dev: true },
{ code: 'twilio', display: 'Twilio', class: SMS }, { code: 'twilio', display: 'Twilio', class: SMS },
{ code: 'mailgun', display: 'Mailgun', class: EMAIL }, { code: 'mailgun', display: 'Mailgun', class: EMAIL },
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: [BTC, ZEC, LTC, DASH, BCH, ETH, XMR] }, { code: 'none', display: 'None', class: ZERO_CONF, cryptos: ALL_CRYPTOS },
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] }, { code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true }, { code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true },
{ code: 'ciphertrace', display: 'CipherTrace', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH] }, { code: 'ciphertrace', display: 'CipherTrace', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH] },

View file

@ -45,6 +45,12 @@ function getLookup (account, number) {
.fetch({ addOns: ['lamassu_ekata'] }) .fetch({ addOns: ['lamassu_ekata'] })
}) })
.then(info => info.addOns.results['lamassu_ekata']) .then(info => info.addOns.results['lamassu_ekata'])
.then(info => {
if (info.status !== 'successful') {
throw new Error(`Twilio error: ${info.message}`)
}
return info
})
.catch(err => { .catch(err => {
if (_.includes(err.code, BAD_NUMBER_CODES)) { if (_.includes(err.code, BAD_NUMBER_CODES)) {
const badNumberError = new Error(err.message) const badNumberError = new Error(err.message)

View file

@ -125,7 +125,11 @@ const validationSchema = Yup.object().shape({
.test( .test(
'unique-name', 'unique-name',
'Machine name is already in use.', 'Machine name is already in use.',
(value, context) => !context.options.context.machineNames.includes(value) (value, context) =>
!R.any(
it => R.equals(R.toLower(it), R.toLower(value)),
context.options.context.machineNames
)
) )
}) })

View file

@ -3,7 +3,7 @@ import { Box } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames' import classnames from 'classnames'
import { endOfToday } from 'date-fns' import { endOfToday } from 'date-fns'
import { subDays } from 'date-fns/fp' import { subDays, format, add, startOfWeek } from 'date-fns/fp'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
@ -43,6 +43,16 @@ const TIME_OPTIONS = {
month: MONTH month: MONTH
} }
const DAY_OPTIONS = R.map(
it => ({
code: R.toLower(it),
display: it
}),
Array.from(Array(7)).map((_, i) =>
format('EEEE', add({ days: i }, startOfWeek(new Date())))
)
)
const GET_TRANSACTIONS = gql` const GET_TRANSACTIONS = gql`
query transactions( query transactions(
$from: Date $from: Date
@ -138,6 +148,9 @@ const Analytics = () => {
const [representing, setRepresenting] = useState(REPRESENTING_OPTIONS[0]) const [representing, setRepresenting] = useState(REPRESENTING_OPTIONS[0])
const [period, setPeriod] = useState(PERIOD_OPTIONS[0]) const [period, setPeriod] = useState(PERIOD_OPTIONS[0])
const [machine, setMachine] = useState(MACHINE_OPTIONS[0]) const [machine, setMachine] = useState(MACHINE_OPTIONS[0])
const [selectedDay, setSelectedDay] = useState(
R.equals(representing.code, 'hourOfTheDay') ? DAY_OPTIONS[0] : null
)
const loading = txLoading || configLoading const loading = txLoading || configLoading
@ -179,15 +192,27 @@ const Analytics = () => {
const filteredData = timeInterval => ({ const filteredData = timeInterval => ({
current: current:
machineTxs.filter( machineTxs.filter(d => {
d => new Date(d.created) >= Date.now() - TIME_OPTIONS[timeInterval] const txDay = new Date(d.created)
) ?? [], const isSameWeekday = !R.isNil(selectedDay)
? R.equals(R.toLower(format('EEEE', txDay)), selectedDay.code)
: true
return isSameWeekday && txDay >= Date.now() - TIME_OPTIONS[timeInterval]
}) ?? [],
previous: previous:
machineTxs.filter( machineTxs.filter(d => {
d => const txDay = new Date(d.created)
new Date(d.created) < Date.now() - TIME_OPTIONS[timeInterval] && const isSameWeekday = !R.isNil(selectedDay)
new Date(d.created) >= Date.now() - 2 * TIME_OPTIONS[timeInterval] ? R.equals(R.toLower(format('EEEE', txDay)), selectedDay.code)
) ?? [] : true
return (
isSameWeekday &&
txDay < Date.now() - TIME_OPTIONS[timeInterval] &&
txDay >= Date.now() - 2 * TIME_OPTIONS[timeInterval]
)
}) ?? []
}) })
const txs = { const txs = {
@ -224,6 +249,13 @@ const Analytics = () => {
) )
} }
const handleRepresentationChange = newRepresentation => {
setRepresenting(newRepresentation)
setSelectedDay(
R.equals(newRepresentation.code, 'hourOfTheDay') ? DAY_OPTIONS[0] : null
)
}
const getGraphInfo = representing => { const getGraphInfo = representing => {
switch (representing.code) { switch (representing.code) {
case 'overTime': case 'overTime':
@ -264,6 +296,9 @@ const Analytics = () => {
machines={machineOptions} machines={machineOptions}
selectedMachine={machine} selectedMachine={machine}
handleMachineChange={setMachine} handleMachineChange={setMachine}
selectedDay={selectedDay}
dayOptions={DAY_OPTIONS}
handleDayChange={setSelectedDay}
timezone={timezone} timezone={timezone}
currency={fiatLocale} currency={fiatLocale}
/> />
@ -296,7 +331,7 @@ const Analytics = () => {
<div className={classes.dropdowns}> <div className={classes.dropdowns}>
<Select <Select
label="Representing" label="Representing"
onSelectedItemChange={setRepresenting} onSelectedItemChange={handleRepresentationChange}
items={REPRESENTING_OPTIONS} items={REPRESENTING_OPTIONS}
default={REPRESENTING_OPTIONS[0]} default={REPRESENTING_OPTIONS[0]}
selectedItem={representing} selectedItem={representing}

View file

@ -1,11 +1,10 @@
import { Box } from '@material-ui/core' import { Box } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles' import { makeStyles } from '@material-ui/core/styles'
import { getTimezoneOffset } from 'date-fns-tz' import { getTimezoneOffset } from 'date-fns-tz'
import { format, add, startOfWeek } from 'date-fns/fp'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { RadioGroup, Select } from 'src/components/inputs' import { Select } from 'src/components/inputs'
import { H2 } from 'src/components/typography' import { H2 } from 'src/components/typography'
import { MINUTE } from 'src/utils/time' import { MINUTE } from 'src/utils/time'
@ -20,16 +19,6 @@ const options = [
{ code: 'hourOfDayVolume', display: 'Volume' } { code: 'hourOfDayVolume', display: 'Volume' }
] ]
const dayOptions = R.map(
it => ({
code: R.toLower(it),
display: it
}),
Array.from(Array(7)).map((_, i) =>
format('EEEE', add({ days: i }, startOfWeek(new Date())))
)
)
const HourOfDayBarGraphHeader = ({ const HourOfDayBarGraphHeader = ({
title, title,
period, period,
@ -37,13 +26,15 @@ const HourOfDayBarGraphHeader = ({
machines, machines,
selectedMachine, selectedMachine,
handleMachineChange, handleMachineChange,
selectedDay,
dayOptions,
handleDayChange,
timezone, timezone,
currency currency
}) => { }) => {
const classes = useStyles() const classes = useStyles()
const [graphType, setGraphType] = useState(options[0].code) const [graphType /*, setGraphType */] = useState(options[0].code)
const [selectedDay, setSelectedDay] = useState(dayOptions[0])
const legend = { const legend = {
cashIn: <div className={classes.cashInIcon}></div>, cashIn: <div className={classes.cashInIcon}></div>,
@ -100,18 +91,18 @@ const HourOfDayBarGraphHeader = ({
</Box> </Box>
</div> </div>
<div className={classes.graphHeaderRight}> <div className={classes.graphHeaderRight}>
<RadioGroup {/* <RadioGroup
options={options} options={options}
className={classes.topMachinesRadio} className={classes.topMachinesRadio}
value={graphType} value={graphType}
onChange={e => setGraphType(e.target.value)} onChange={e => setGraphType(e.target.value)}
/> /> */}
<Select <Select
label="Day of the week" label="Day of the week"
items={dayOptions} items={dayOptions}
default={dayOptions[0]} default={dayOptions[0]}
selectedItem={selectedDay} selectedItem={selectedDay}
onSelectedItemChange={setSelectedDay} onSelectedItemChange={handleDayChange}
/> />
<Select <Select
label="Machines" label="Machines"

View file

@ -3,7 +3,6 @@ import { makeStyles } from '@material-ui/core/styles'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { RadioGroup } from 'src/components/inputs'
import { H2 } from 'src/components/typography' import { H2 } from 'src/components/typography'
import styles from '../../Analytics.styles' import styles from '../../Analytics.styles'
@ -28,7 +27,7 @@ const TopMachinesBarGraphHeader = ({
}) => { }) => {
const classes = useStyles() const classes = useStyles()
const [graphType, setGraphType] = useState(options[0].code) const [graphType /*, setGraphType */] = useState(options[0].code)
const legend = { const legend = {
cashIn: <div className={classes.cashInIcon}></div>, cashIn: <div className={classes.cashInIcon}></div>,
@ -46,12 +45,12 @@ const TopMachinesBarGraphHeader = ({
</Box> </Box>
</div> </div>
<div className={classes.graphHeaderRight}> <div className={classes.graphHeaderRight}>
<RadioGroup {/* <RadioGroup
options={options} options={options}
className={classes.topMachinesRadio} className={classes.topMachinesRadio}
value={graphType} value={graphType}
onChange={e => setGraphType(e.target.value)} onChange={e => setGraphType(e.target.value)}
/> /> */}
</div> </div>
</div> </div>
<Graph <Graph

View file

@ -17,7 +17,7 @@ const MODAL_WIDTH = 554
const MODAL_HEIGHT = 520 const MODAL_HEIGHT = 520
const Wizard = ({ machine, locale, onClose, save, error }) => { const Wizard = ({ machine, locale, onClose, save, error }) => {
const LAST_STEP = machine.numberOfCassettes + 2 const LAST_STEP = machine.numberOfCassettes + 1
const [{ step, config }, setState] = useState({ const [{ step, config }, setState] = useState({
step: 0, step: 0,
config: { active: true } config: { active: true }
@ -46,34 +46,21 @@ const Wizard = ({ machine, locale, onClose, save, error }) => {
}) })
} }
const steps = []
R.until( const steps = R.map(
R.gt(R.__, machine.numberOfCassettes), it => ({
it => { type: `cassette${it}`,
steps.push({ display: `Cassette ${it}`,
type: `cassette${it}`, component: Autocomplete,
display: `Cassette ${it}`, inputProps: {
component: Autocomplete, options: options,
inputProps: { labelProp: 'display',
options: options, valueProp: 'code'
labelProp: 'display', }
valueProp: 'code' }),
} R.range(1, machine.numberOfCassettes + 1)
})
return R.add(1, it)
},
1
) )
steps.push({
type: 'zeroConfLimit',
display: '0-conf Limit',
schema: Yup.object().shape({
zeroConfLimit: Yup.number().required()
})
})
const schema = () => const schema = () =>
Yup.object().shape({ Yup.object().shape({
cassette1: Yup.number().required(), cassette1: Yup.number().required(),
@ -113,7 +100,7 @@ const Wizard = ({ machine, locale, onClose, save, error }) => {
name={machine.name} name={machine.name}
numberOfCassettes={machine.numberOfCassettes} numberOfCassettes={machine.numberOfCassettes}
error={error} error={error}
lastStep={isLastStep} isLastStep={isLastStep}
steps={steps} steps={steps}
fiatCurrency={locale.fiatCurrency} fiatCurrency={locale.fiatCurrency}
options={options} options={options}

View file

@ -44,7 +44,7 @@ const WizardStep = ({
step, step,
schema, schema,
error, error,
lastStep, isLastStep,
onContinue, onContinue,
steps, steps,
fiatCurrency, fiatCurrency,
@ -53,7 +53,7 @@ const WizardStep = ({
}) => { }) => {
const classes = useStyles() const classes = useStyles()
const label = lastStep ? 'Finish' : 'Next' const label = isLastStep ? 'Finish' : 'Next'
return ( return (
<> <>
@ -62,7 +62,7 @@ const WizardStep = ({
<Stepper steps={steps.length + 1} currentStep={step} /> <Stepper steps={steps.length + 1} currentStep={step} />
</div> </div>
{step <= numberOfCassettes && ( {!isLastStep && (
<Formik <Formik
validateOnBlur={false} validateOnBlur={false}
validateOnChange={false} validateOnChange={false}
@ -121,46 +121,7 @@ const WizardStep = ({
</Formik> </Formik>
)} )}
{step === numberOfCassettes + 1 && ( {isLastStep && (
<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}> <div className={classes.disclaimer}>
<Info2 className={classes.title}>Cash Cassette Bill Count</Info2> <Info2 className={classes.title}>Cash Cassette Bill Count</Info2>
<P> <P>

View file

@ -745,7 +745,7 @@ const RetrieveDataDialog = ({
</DialogContent> </DialogContent>
{error && ( {error && (
<ErrorMessage className={classes.errorMessage}> <ErrorMessage className={classes.errorMessage}>
Failed to save Failed to fetch additional data
</ErrorMessage> </ErrorMessage>
)} )}
<DialogActions className={classes.dialogActions}> <DialogActions className={classes.dialogActions}>

View file

@ -34,8 +34,8 @@ const validationSchema = existingRequirements =>
'unique-name', 'unique-name',
'A custom information requirement with that name already exists', 'A custom information requirement with that name already exists',
(value, _context) => (value, _context) =>
!R.includes( !R.any(
value, it => R.equals(R.toLower(it), R.toLower(value)),
R.map(it => it.customRequest.name, existingRequirements) R.map(it => it.customRequest.name, existingRequirements)
) )
) )

View file

@ -79,7 +79,10 @@ const Wizard = ({
}), }),
...zeroConfs ...zeroConfs
}, },
{ type: 'zeroConfLimit', name: '0-conf limit', ...zeroConfs } {
type: 'zeroConfLimit',
name: '0-conf limit'
}
]) ])
: commonWizardSteps : commonWizardSteps

View file

@ -112,8 +112,7 @@ const WizardStep = ({
radioClassName={classes.radio} radioClassName={classes.radio}
/> />
)} )}
{/* Hack to support optional 0conf setup */ {type === 'zeroConfLimit' && (
isLastStep && step === maxSteps && (
<Formik <Formik
validateOnBlur={false} validateOnBlur={false}
validateOnChange={true} validateOnChange={true}

View file

@ -70,6 +70,20 @@ const AllSet = ({ data: currentData, doContinue }) => {
return saveConfig({ variables: { config } }) return saveConfig({ variables: { config } })
} }
const presentableData = R.pipe(
R.omit(['coin', 'zeroConf', 'zeroConfLimit']),
toNamespace(coin)
)(currentData)
const presentableElements = R.filter(
R.pipe(
R.prop('name'),
R.flip(R.includes)(['zeroConf', 'zeroConfLimit']),
R.not()
),
getElements(cryptoCurrencies, accountsConfig, null, true)
)
return ( return (
<> <>
<H4 className={error && classes.error}>All set</H4> <H4 className={error && classes.error}>All set</H4>
@ -82,8 +96,8 @@ const AllSet = ({ data: currentData, doContinue }) => {
titleLg titleLg
name="All set" name="All set"
namespaces={[coin]} namespaces={[coin]}
data={toNamespace(coin, R.omit('coin', currentData))} data={presentableData}
elements={getElements(cryptoCurrencies, accountsConfig, true)} elements={presentableElements}
/> />
<Button size="lg" onClick={save} className={classes.button}> <Button size="lg" onClick={save} className={classes.button}>
Continue Continue