From eb6e0909361d42ba46160f25e0339fe93259c990 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Thu, 16 Dec 2021 11:31:03 +0100
Subject: [PATCH 01/51] fix: redirect to m-status on invalid machine ID
---
.../src/pages/Machines/Machines.js | 40 ++++++++++++++++---
new-lamassu-admin/src/routing/routes.js | 4 +-
2 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Machines/Machines.js b/new-lamassu-admin/src/pages/Machines/Machines.js
index 89c13f24..19381f20 100644
--- a/new-lamassu-admin/src/pages/Machines/Machines.js
+++ b/new-lamassu-admin/src/pages/Machines/Machines.js
@@ -7,7 +7,7 @@ import classnames from 'classnames'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React from 'react'
-import { Link, useLocation } from 'react-router-dom'
+import { Link, useLocation, useHistory } from 'react-router-dom'
import { TL1, TL2, Label3 } from 'src/components/typography'
@@ -50,13 +50,43 @@ const GET_INFO = gql`
}
`
+const GET_MACHINES = gql`
+ {
+ machines {
+ name
+ deviceId
+ }
+ }
+`
+
const getMachineID = path => path.slice(path.lastIndexOf('/') + 1)
-const Machines = () => {
+const MachineRoute = () => {
const location = useLocation()
+ const history = useHistory()
+
+ const id = getMachineID(location.pathname)
+
+ const { loading } = useQuery(GET_MACHINES, {
+ onCompleted: data => {
+ const machines = data.machines
+ const machineFound = machines.map(m => m.deviceId).includes(id)
+
+ if (!machineFound) return history.push('/maintenance/machine-status')
+ }
+ })
+
+ const reload = () => {
+ return history.push(location.pathname)
+ }
+
+ return !loading &&
+}
+
+const Machines = ({ id, reload }) => {
const { data, loading, refetch } = useQuery(GET_INFO, {
variables: {
- deviceId: getMachineID(location.pathname)
+ deviceId: id
}
})
const classes = useStyles()
@@ -85,7 +115,7 @@ const Machines = () => {
{machineName}
-
+
@@ -119,4 +149,4 @@ const Machines = () => {
)
}
-export default Machines
+export default MachineRoute
diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js
index 18876686..5aa939bb 100644
--- a/new-lamassu-admin/src/routing/routes.js
+++ b/new-lamassu-admin/src/routing/routes.js
@@ -17,7 +17,7 @@ import Register from 'src/pages/Authentication/Register'
import Reset2FA from 'src/pages/Authentication/Reset2FA'
import ResetPassword from 'src/pages/Authentication/ResetPassword'
import Dashboard from 'src/pages/Dashboard'
-import Machines from 'src/pages/Machines'
+import MachineRoute from 'src/pages/Machines'
import Wizard from 'src/pages/Wizard'
import PrivateRoute from './PrivateRoute'
@@ -140,7 +140,7 @@ const Routes = () => {
}
/>
-
+
{/* */}
From 586ad4879b0ab120d4832aaef92cb08b7ef9a533 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Thu, 16 Dec 2021 11:41:09 +0100
Subject: [PATCH 02/51] chore: revert unnecessary changes
---
new-lamassu-admin/src/routing/routes.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js
index 5aa939bb..18876686 100644
--- a/new-lamassu-admin/src/routing/routes.js
+++ b/new-lamassu-admin/src/routing/routes.js
@@ -17,7 +17,7 @@ import Register from 'src/pages/Authentication/Register'
import Reset2FA from 'src/pages/Authentication/Reset2FA'
import ResetPassword from 'src/pages/Authentication/ResetPassword'
import Dashboard from 'src/pages/Dashboard'
-import MachineRoute from 'src/pages/Machines'
+import Machines from 'src/pages/Machines'
import Wizard from 'src/pages/Wizard'
import PrivateRoute from './PrivateRoute'
@@ -140,7 +140,7 @@ const Routes = () => {
}
/>
-
+
{/* */}
From f620927f3bddb9bdeac016280c11a02e981f5b91 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Thu, 16 Dec 2021 12:35:27 +0100
Subject: [PATCH 03/51] fix: use state instead apollo `loading` result
---
new-lamassu-admin/src/pages/Machines/Machines.js | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Machines/Machines.js b/new-lamassu-admin/src/pages/Machines/Machines.js
index 19381f20..c4b5635f 100644
--- a/new-lamassu-admin/src/pages/Machines/Machines.js
+++ b/new-lamassu-admin/src/pages/Machines/Machines.js
@@ -6,7 +6,7 @@ import NavigateNextIcon from '@material-ui/icons/NavigateNext'
import classnames from 'classnames'
import gql from 'graphql-tag'
import * as R from 'ramda'
-import React from 'react'
+import React, { useState } from 'react'
import { Link, useLocation, useHistory } from 'react-router-dom'
import { TL1, TL2, Label3 } from 'src/components/typography'
@@ -67,12 +67,16 @@ const MachineRoute = () => {
const id = getMachineID(location.pathname)
- const { loading } = useQuery(GET_MACHINES, {
+ const [loading, setLoading] = useState(true)
+
+ useQuery(GET_MACHINES, {
onCompleted: data => {
const machines = data.machines
const machineFound = machines.map(m => m.deviceId).includes(id)
if (!machineFound) return history.push('/maintenance/machine-status')
+
+ setLoading(false)
}
})
From 79c2aa8988e24bb65947ec35f777f0ad7bc9da18 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Tue, 21 Dec 2021 18:17:22 +0100
Subject: [PATCH 04/51] fix: cash cassette wizard autofocus
---
new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
index d28081c5..db1e242e 100644
--- a/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
+++ b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
@@ -277,6 +277,7 @@ const WizardStep = ({
placeholder={originalCassetteCount.toString()}
name={cassetteField}
className={classes.cashboxBills}
+ autoFocus
/>
{cassetteDenomination} {fiatCurrency} bills loaded
From 326e395aa095ed84af96db4ff01527a38301dfdd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Wed, 22 Dec 2021 15:03:30 +0000
Subject: [PATCH 05/51] fix: customers page filter deletion
---
.../src/pages/Customers/Customers.js | 49 ++++++++++++++-----
1 file changed, 38 insertions(+), 11 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js
index f263de86..09565cb4 100644
--- a/new-lamassu-admin/src/pages/Customers/Customers.js
+++ b/new-lamassu-admin/src/pages/Customers/Customers.js
@@ -51,6 +51,9 @@ const GET_CUSTOMERS = gql`
const useBaseStyles = makeStyles(baseStyles)
+const getFiltersObj = filters =>
+ R.reduce((s, f) => ({ ...s, [f.type]: f.value }), {}, filters)
+
const Customers = () => {
const baseStyles = useBaseStyles()
const history = useHistory()
@@ -82,12 +85,7 @@ const Customers = () => {
)
const onFilterChange = filters => {
- const filtersObject = R.compose(
- R.mergeAll,
- R.map(f => ({
- [f.type]: f.value
- }))
- )(filters)
+ const filtersObject = getFiltersObj(filters)
setFilters(filters)
@@ -101,10 +99,38 @@ const Customers = () => {
refetch && refetch()
}
- const onFilterDelete = filter =>
- setFilters(
- R.filter(f => !R.whereEq(R.pick(['type', 'value'], f), filter))(filters)
- )
+ const onFilterDelete = filter => {
+ const newFilters = R.filter(
+ f => !R.whereEq(R.pick(['type', 'value'], f), filter)
+ )(filters)
+
+ setFilters(newFilters)
+
+ const filtersObject = getFiltersObj(newFilters)
+
+ setVariables({
+ phone: filtersObject.phone,
+ name: filtersObject.name,
+ address: filtersObject.address,
+ id: filtersObject.id
+ })
+
+ refetch && refetch()
+ }
+
+ const deleteAllFilters = () => {
+ setFilters([])
+ const filtersObject = getFiltersObj([])
+
+ setVariables({
+ phone: filtersObject.phone,
+ name: filtersObject.name,
+ address: filtersObject.address,
+ id: filtersObject.id
+ })
+
+ refetch && refetch()
+ }
const filterOptions = R.path(['customerFilters'])(filtersResponse)
@@ -131,9 +157,10 @@ const Customers = () => {
/>
{filters.length > 0 && (
)}
Date: Wed, 22 Dec 2021 22:02:57 +0100
Subject: [PATCH 06/51] fix: use `useEfect` for focus
---
.../src/pages/Maintenance/Wizard/WizardStep.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
index db1e242e..cb5a1adf 100644
--- a/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
+++ b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
@@ -139,6 +139,11 @@ const WizardStep = ({
R.clamp(0, 100)
)
+ const textInput = React.useRef(null)
+ React.useEffect(() => {
+ textInput.current?.children[0].children[0]?.focus()
+ }, [step])
+
return (
@@ -277,7 +282,7 @@ const WizardStep = ({
placeholder={originalCassetteCount.toString()}
name={cassetteField}
className={classes.cashboxBills}
- autoFocus
+ innerRef={textInput}
/>
{cassetteDenomination} {fiatCurrency} bills loaded
From 5b7b342788925b805661ef304e0df25d09eb1f60 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Thu, 23 Dec 2021 19:24:36 +0100
Subject: [PATCH 07/51] fix: machine table row
---
.../Dashboard/SystemStatus/MachinesTable.js | 22 +++++++++----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemStatus/MachinesTable.js b/new-lamassu-admin/src/pages/Dashboard/SystemStatus/MachinesTable.js
index e97805f9..ee85d4af 100644
--- a/new-lamassu-admin/src/pages/Dashboard/SystemStatus/MachinesTable.js
+++ b/new-lamassu-admin/src/pages/Dashboard/SystemStatus/MachinesTable.js
@@ -128,17 +128,17 @@ const MachinesTable = ({ machines = [], numToRender }) => {
onClick={() => redirect(machine)}
className={classnames(classes.row)}
key={machine.deviceId + idx}>
-
- {machine.name}
- redirect(machine)}
- />
+
+
+ {machine.name}
+ redirect(machine)}
+ />
+
From deef6e52a37aba7d5e24339d4a04d2744c39fe53 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Fri, 24 Dec 2021 23:30:25 +0100
Subject: [PATCH 08/51] fix: throw error on missing resource
---
lib/machine-loader.js | 58 ++++-----
.../src/pages/Machines/Machines.js | 117 ++++++++----------
2 files changed, 79 insertions(+), 96 deletions(-)
diff --git a/lib/machine-loader.js b/lib/machine-loader.js
index b2ae3afb..5790673f 100644
--- a/lib/machine-loader.js
+++ b/lib/machine-loader.js
@@ -12,30 +12,35 @@ const configManager = require('./new-config-manager')
const settingsLoader = require('./new-settings-loader')
const notifierUtils = require('./notifier/utils')
const notifierQueries = require('./notifier/queries')
+const { ApolloError } = require('apollo-server-errors');
const fullyFunctionalStatus = { label: 'Fully functional', type: 'success' }
const unresponsiveStatus = { label: 'Unresponsive', type: 'error' }
const stuckStatus = { label: 'Stuck', type: 'error' }
+function toMachineObject (r) {
+ return {
+ deviceId: r.device_id,
+ 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),
+ lastPing: new Date(r.last_online),
+ name: r.name,
+ paired: r.paired
+ // TODO: we shall start using this JSON field at some point
+ // location: r.location,
+ }
+}
+
function getMachines () {
return db.any('SELECT * FROM devices WHERE display=TRUE ORDER BY created')
- .then(rr => rr.map(r => ({
- deviceId: r.device_id,
- 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),
- lastPing: new Date(r.last_online),
- name: r.name,
- // TODO: we shall start using this JSON field at some point
- // location: r.location,
- paired: r.paired
- })))
+ .then(rr => rr.map(toMachineObject))
}
function getConfig (defaultConfig) {
@@ -100,21 +105,10 @@ function getMachineName (machineId) {
function getMachine (machineId, config) {
const sql = 'SELECT * FROM devices WHERE device_id=$1'
- const queryMachine = db.oneOrNone(sql, [machineId]).then(r => ({
- deviceId: r.device_id,
- 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),
- lastPing: new Date(r.last_online),
- name: r.name,
- paired: r.paired
- }))
+ const queryMachine = db.oneOrNone(sql, [machineId]).then(r => {
+ if (r === null) throw new ApolloError('Resource doesn\'t exist', 'NOT_FOUND')
+ else return toMachineObject(r)
+ })
return Promise.all([queryMachine, dbm.machineEvents(), config])
.then(([machine, events, config]) => {
diff --git a/new-lamassu-admin/src/pages/Machines/Machines.js b/new-lamassu-admin/src/pages/Machines/Machines.js
index c4b5635f..66fcf19d 100644
--- a/new-lamassu-admin/src/pages/Machines/Machines.js
+++ b/new-lamassu-admin/src/pages/Machines/Machines.js
@@ -50,15 +50,6 @@ const GET_INFO = gql`
}
`
-const GET_MACHINES = gql`
- {
- machines {
- name
- deviceId
- }
- }
-`
-
const getMachineID = path => path.slice(path.lastIndexOf('/') + 1)
const MachineRoute = () => {
@@ -69,14 +60,15 @@ const MachineRoute = () => {
const [loading, setLoading] = useState(true)
- useQuery(GET_MACHINES, {
+ const { data, refetch } = useQuery(GET_INFO, {
onCompleted: data => {
- const machines = data.machines
- const machineFound = machines.map(m => m.deviceId).includes(id)
-
- if (!machineFound) return history.push('/maintenance/machine-status')
+ if (data.machine === null)
+ return history.push('/maintenance/machine-status')
setLoading(false)
+ },
+ variables: {
+ deviceId: id
}
})
@@ -84,15 +76,14 @@ const MachineRoute = () => {
return history.push(location.pathname)
}
- return !loading &&
+ return (
+ !loading && (
+
+ )
+ )
}
-const Machines = ({ id, reload }) => {
- const { data, loading, refetch } = useQuery(GET_INFO, {
- variables: {
- deviceId: id
- }
- })
+const Machines = ({ data, refetch, reload }) => {
const classes = useStyles()
const timezone = R.path(['config', 'locale_timezone'], data) ?? {}
@@ -104,52 +95,50 @@ const Machines = ({ id, reload }) => {
const machineID = R.path(['deviceId'])(machine) ?? null
return (
- !loading && (
-
-
-
-
- }>
-
-
- Dashboard
-
-
-
- {machineName}
-
-
-
-
-
-
-
-
-
- {'Details'}
-
-
-
- {'Cash cassettes'}
-
-
-
- {'Latest transactions'}
-
-
-
- {'Commissions'}
-
-
+
+
+
+
+ }>
+
+
+ Dashboard
+
+
+
+ {machineName}
+
+
+
- )
+
+
+
+ {'Details'}
+
+
+
+ {'Cash cassettes'}
+
+
+
+ {'Latest transactions'}
+
+
+
+ {'Commissions'}
+
+
+
+
+
)
}
From 6521d45ab51caeaf7fb58f05ebdeff3773abf9e3 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Fri, 24 Dec 2021 23:47:31 +0100
Subject: [PATCH 09/51] fix: conflicts
---
.../src/pages/Machines/Machines.js | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Machines/Machines.js b/new-lamassu-admin/src/pages/Machines/Machines.js
index 66fcf19d..7fdbd5cb 100644
--- a/new-lamassu-admin/src/pages/Machines/Machines.js
+++ b/new-lamassu-admin/src/pages/Machines/Machines.js
@@ -20,7 +20,7 @@ import styles from './Machines.styles'
const useStyles = makeStyles(styles)
const GET_INFO = gql`
- query getMachine($deviceId: ID!) {
+ query getMachine($deviceId: ID!, $billFilters: JSONObject) {
machine(deviceId: $deviceId) {
name
deviceId
@@ -46,6 +46,12 @@ const GET_INFO = gql`
note
}
}
+ bills(filters: $billFilters) {
+ id
+ fiat
+ deviceId
+ created
+ }
config
}
`
@@ -69,6 +75,10 @@ const MachineRoute = () => {
},
variables: {
deviceId: id
+ },
+ billFilters: {
+ deviceId: id,
+ batch: 'none'
}
})
@@ -83,7 +93,7 @@ const MachineRoute = () => {
)
}
-const Machines = ({ data, refetch, reload }) => {
+const Machines = ({ data, refetch, reload, bills }) => {
const classes = useStyles()
const timezone = R.path(['config', 'locale_timezone'], data) ?? {}
@@ -121,11 +131,12 @@ const Machines = ({ data, refetch, reload }) => {
- {'Cash cassettes'}
+ {'Cash box & cassettes'}
From 258d727b1aab5ce43755255c5c90193f79359766 Mon Sep 17 00:00:00 2001
From: Neal
Date: Mon, 27 Dec 2021 18:39:12 -0500
Subject: [PATCH 10/51] chore: update zec, xmr, eth versions
---
lib/blockchain/common.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js
index 0f0625e6..ccec88a6 100644
--- a/lib/blockchain/common.js
+++ b/lib/blockchain/common.js
@@ -29,12 +29,12 @@ const BINARIES = {
dir: 'bitcoin-22.0/bin'
},
ETH: {
- url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.13-7a0c19f8.tar.gz',
- dir: 'geth-linux-amd64-1.10.13-7a0c19f8'
+ url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.14-11a3a350.tar.gz',
+ dir: 'geth-linux-amd64-1.10.14-11a3a350'
},
ZEC: {
- url: 'https://z.cash/downloads/zcash-4.5.1-1-linux64-debian-stretch.tar.gz',
- dir: 'zcash-4.5.1-1/bin'
+ url: 'https://z.cash/downloads/zcash-4.6.0-linux64-debian-stretch.tar.gz',
+ dir: 'zcash-4.6.0/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',
@@ -50,8 +50,8 @@ const BINARIES = {
files: [['bitcoind', 'bitcoincashd'], ['bitcoin-cli', 'bitcoincash-cli']]
},
XMR: {
- url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.17.2.0.tar.bz2',
- dir: 'monero-x86_64-linux-gnu-v0.17.2.0',
+ url: 'https://downloads.getmonero.org/cli/monero-linux-x64-v0.17.3.0.tar.bz2',
+ dir: 'monero-x86_64-linux-gnu-v0.17.3.0',
files: [['monerod', 'monerod'], ['monero-wallet-rpc', 'monero-wallet-rpc']]
}
}
From 4d5d883532661ca5c92b7f62725912fe814151ef Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Tue, 28 Dec 2021 17:43:33 +0100
Subject: [PATCH 11/51] fix: capitalize labels
---
new-lamassu-admin/src/routing/lamassu.routes.js | 2 +-
new-lamassu-admin/src/routing/pazuz.routes.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/new-lamassu-admin/src/routing/lamassu.routes.js b/new-lamassu-admin/src/routing/lamassu.routes.js
index 2626e30b..e647c65b 100644
--- a/new-lamassu-admin/src/routing/lamassu.routes.js
+++ b/new-lamassu-admin/src/routing/lamassu.routes.js
@@ -130,7 +130,7 @@ const getLamassuRoutes = () => [
},
{
key: 'services',
- label: '3rd party services',
+ label: '3rd Party Services',
route: '/settings/3rd-party-services',
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
component: Services
diff --git a/new-lamassu-admin/src/routing/pazuz.routes.js b/new-lamassu-admin/src/routing/pazuz.routes.js
index eaed555c..153d0205 100644
--- a/new-lamassu-admin/src/routing/pazuz.routes.js
+++ b/new-lamassu-admin/src/routing/pazuz.routes.js
@@ -132,7 +132,7 @@ const getPazuzRoutes = () => [
},
{
key: 'services',
- label: '3rd party services',
+ label: '3rd Party Services',
route: '/settings/3rd-party-services',
allowedRoles: [ROLES.USER, ROLES.SUPERUSER],
component: Services
From 4a630f0f5348867bdb8843b1768546a189433f15 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Tue, 28 Dec 2021 20:09:43 +0100
Subject: [PATCH 12/51] fix: replace `client` and `username` with `email`
fix: error handling in lamassu-register
---
bin/lamassu-register | 19 +++++++++++--------
.../src/pages/Authentication/LoginState.js | 19 ++++++++++---------
.../src/pages/Authentication/Register.js | 4 ++++
.../src/pages/Authentication/Setup2FAState.js | 11 +++++------
4 files changed, 30 insertions(+), 23 deletions(-)
diff --git a/bin/lamassu-register b/bin/lamassu-register
index c24a59b6..7d5284bc 100755
--- a/bin/lamassu-register
+++ b/bin/lamassu-register
@@ -2,6 +2,7 @@
const { asyncLocalStorage, defaultStore } = require('../lib/async-storage')
const userManagement = require('../lib/new-admin/graphql/modules/userManagement')
+const authErrors = require('../lib/new-admin/graphql/errors/authentication')
const options = require('../lib/options')
const name = process.argv[2]
@@ -14,29 +15,25 @@ if (!domain) {
}
if (!name || !role) {
- console.log('Usage: lamassu-register ')
+ console.log('Usage: lamassu-register ')
+ console.log(' must be \'user\' or \'superuser\'')
process.exit(2)
}
const emailRegex = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
if (!emailRegex.test(name)) {
- console.log('Usage: should be in an email format')
+ console.log('Usage: must be in an email format')
process.exit(2)
}
if (role !== 'user' && role !== 'superuser') {
- console.log('Usage: has two possible values: user | superuser')
+ console.log('Usage: must be \'user\' or \'superuser\'')
process.exit(2)
}
asyncLocalStorage.run(defaultStore(), () => {
userManagement.createRegisterToken(name, role).then(token => {
- if (!token) {
- console.log(`A user named ${name} already exists!`)
- process.exit(2)
- }
-
if (domain === 'localhost') {
console.log(`https://${domain}:3001/register?t=${token.token}`)
} else {
@@ -45,6 +42,12 @@ asyncLocalStorage.run(defaultStore(), () => {
process.exit(0)
}).catch(err => {
+
+ if (err instanceof authErrors.UserAlreadyExistsError){
+ console.log(`A user with email ${name} already exists!`)
+ process.exit(2)
+ }
+
console.log('Error: %s', err)
process.exit(3)
})
diff --git a/new-lamassu-admin/src/pages/Authentication/LoginState.js b/new-lamassu-admin/src/pages/Authentication/LoginState.js
index aaa91fe9..4cc4444b 100644
--- a/new-lamassu-admin/src/pages/Authentication/LoginState.js
+++ b/new-lamassu-admin/src/pages/Authentication/LoginState.js
@@ -46,23 +46,24 @@ const GET_USER_DATA = gql`
`
const validationSchema = Yup.object().shape({
- client: Yup.string()
- .required('Client field is required!')
- .email('Username field should be in an email format!'),
+ email: Yup.string()
+ .label('Email')
+ .required()
+ .email(),
password: Yup.string().required('Password field is required'),
rememberMe: Yup.boolean()
})
const initialValues = {
- client: '',
+ email: '',
password: '',
rememberMe: false
}
const getErrorMsg = (formikErrors, formikTouched, mutationError) => {
if (!formikErrors || !formikTouched) return null
- if (mutationError) return 'Invalid login/password combination'
- if (formikErrors.client && formikTouched.client) return formikErrors.client
+ if (mutationError) return 'Invalid email/password combination'
+ if (formikErrors.email && formikTouched.email) return formikErrors.email
if (formikErrors.password && formikTouched.password)
return formikErrors.password
return null
@@ -142,13 +143,13 @@ const LoginState = ({ state, dispatch, strategy }) => {
validationSchema={validationSchema}
initialValues={initialValues}
onSubmit={values =>
- submitLogin(values.client, values.password, values.rememberMe)
+ submitLogin(values.email, values.password, values.rememberMe)
}>
{({ errors, touched }) => (
diff --git a/new-lamassu-admin/src/pages/Authentication/Setup2FAState.js b/new-lamassu-admin/src/pages/Authentication/Setup2FAState.js
index 410b49e4..618ed89e 100644
--- a/new-lamassu-admin/src/pages/Authentication/Setup2FAState.js
+++ b/new-lamassu-admin/src/pages/Authentication/Setup2FAState.js
@@ -140,14 +140,13 @@ const Setup2FAState = ({ state, dispatch }) => {
<>
- We detected that this account does not have its two-factor
- authentication enabled. In order to protect the resources in the
- system, a two-factor authentication is enforced.
+ This account does not yet have two-factor authentication enabled. To
+ secure the admin, two-factor authentication is required.
- To finish this process, please scan the following QR code or insert
- the secret further below on an authentication app of your choice,
- such as Google Authenticator or Authy.
+ To complete the registration process, scan the following QR code or
+ insert the secret below on a 2FA app, such as Google Authenticator
+ or AndOTP.
From fab755dc629086f59e9696964d1b8c51f7e03dd4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?=
Date: Thu, 30 Dec 2021 14:23:14 +0000
Subject: [PATCH 13/51] fix: bitcoind install
---
lib/blockchain/install.js | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/lib/blockchain/install.js b/lib/blockchain/install.js
index 8596d8f9..554e3951 100644
--- a/lib/blockchain/install.js
+++ b/lib/blockchain/install.js
@@ -71,7 +71,10 @@ function processCryptos (codes) {
) | crontab -`
common.es(rsyncCmd)
- _.forEach(updateCrypto, selectedCryptos)
+ _.forEach(c => {
+ updateCrypto(c)
+ common.es(`sudo supervisorctl start ${c.code}`)
+ }, selectedCryptos)
logger.info('Installation complete.')
}
@@ -98,7 +101,9 @@ function setupCrypto (crypto) {
function updateCrypto (crypto) {
if (!common.isUpdateDependent(crypto.cryptoCode)) return
const cryptoPlugin = plugin(crypto)
- cryptoPlugin.updateCore(common.getBinaries(crypto.cryptoCode))
+ const status = common.es(`sudo supervisorctl status ${crypto.code} | awk '{ print $2 }'`).trim()
+ const isCurrentlyRunning = status === 'RUNNING'
+ cryptoPlugin.updateCore(common.getBinaries(crypto.cryptoCode), isCurrentlyRunning)
}
function plugin (crypto) {
From 0a27c14079e886cecb2aab5e85bd0cbb60069c26 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?=
Date: Thu, 30 Dec 2021 14:26:43 +0000
Subject: [PATCH 14/51] fix: Monero install
---
lib/blockchain/common.js | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js
index 0f0625e6..273515e2 100644
--- a/lib/blockchain/common.js
+++ b/lib/blockchain/common.js
@@ -123,9 +123,7 @@ function fetchAndInstall (coinRec) {
const binDir = requiresUpdate ? binaries.defaultDir : binaries.dir
es(`wget -q ${url}`)
- es(`echo ${downloadFile} | awk -F. '{print $NF}'`) === 'bz2'
- ? es(`tar -xf ${downloadFile}`)
- : es(`tar -xzf ${downloadFile}`)
+ es(`tar -xf ${downloadFile}`)
if (_.isEmpty(binaries.files)) {
es(`sudo cp ${binDir}/* /usr/local/bin`)
From ba7b1d3d97bf02ef29fea507f9fd99ec9f9809b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?=
Date: Thu, 30 Dec 2021 15:16:18 +0000
Subject: [PATCH 15/51] refactor: filter Tether/USDT from install candidates
---
lib/blockchain/install.js | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/lib/blockchain/install.js b/lib/blockchain/install.js
index 554e3951..efc48107 100644
--- a/lib/blockchain/install.js
+++ b/lib/blockchain/install.js
@@ -113,15 +113,18 @@ function plugin (crypto) {
}
function run () {
- const choices = _.map(c => {
- const checked = isInstalledSoftware(c) && isInstalledVolume(c)
- return {
- name: c.display,
- value: c.code,
- checked,
- disabled: checked && 'Installed'
- }
- }, cryptos)
+ const choices = _.flow([
+ _.filter(c => c.type !== 'erc-20'),
+ _.map(c => {
+ const checked = isInstalledSoftware(c) && isInstalledVolume(c)
+ return {
+ name: c.display,
+ value: c.code,
+ checked,
+ disabled: checked && 'Installed'
+ }
+ }),
+ ])(cryptos)
const questions = []
From 09f62e3a16a41c34f6ecb2396f1c57a074027881 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?=
Date: Thu, 30 Dec 2021 18:05:30 +0000
Subject: [PATCH 16/51] chore: rename "Ethereum" to "Ethereum and/or USDT"
Only for the `lamassu-coins` script.
---
lib/blockchain/install.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/blockchain/install.js b/lib/blockchain/install.js
index efc48107..717c9945 100644
--- a/lib/blockchain/install.js
+++ b/lib/blockchain/install.js
@@ -117,8 +117,9 @@ function run () {
_.filter(c => c.type !== 'erc-20'),
_.map(c => {
const checked = isInstalledSoftware(c) && isInstalledVolume(c)
+ const name = c.code === 'ethereum' ? 'Ethereum and/or USDT' : c.display
return {
- name: c.display,
+ name,
value: c.code,
checked,
disabled: checked && 'Installed'
From 33052cddb9cea2d4a75a12dfc4873190fb19db9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Tue, 4 Jan 2022 16:03:38 +0000
Subject: [PATCH 17/51] fix: session token retriever
---
lib/new-admin/graphql/modules/userManagement.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/new-admin/graphql/modules/userManagement.js b/lib/new-admin/graphql/modules/userManagement.js
index 13c42e70..362e150f 100644
--- a/lib/new-admin/graphql/modules/userManagement.js
+++ b/lib/new-admin/graphql/modules/userManagement.js
@@ -240,7 +240,7 @@ const reset2FA = (token, userID, code, context) => {
}
const getToken = context => {
- if (_.isNil(context.req.cookies.lid) || _.isNil(context.req.session.user.id))
+ if (_.isNil(context.req.cookies['lamassu_sid']) || _.isNil(context.req.session.user.id))
throw new authErrors.AuthenticationError('Authentication failed')
return context.req.session.user.id
From b41fe5e89423fa777c10df150387c103f8abe04e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Tue, 4 Jan 2022 20:05:47 +0000
Subject: [PATCH 18/51] fix: incorrect useMemo on a function
---
.../pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js
index f2eb4e78..ef092185 100644
--- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js
+++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js
@@ -62,7 +62,7 @@ const Graph = ({ data, timeFrame, timezone }) => {
[]
)
- const filterDay = useMemo(
+ const filterDay = useCallback(
x => (timeFrame === 'day' ? x.getUTCHours() === 0 : x.getUTCDate() === 1),
[timeFrame]
)
From 13c4603de52b27abf4124730a2c0979494b0a351 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Fri, 7 Jan 2022 17:27:39 +0100
Subject: [PATCH 19/51] fix: revert commit
---
.../src/pages/Maintenance/Wizard/WizardStep.js | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
index cb5a1adf..db1e242e 100644
--- a/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
+++ b/new-lamassu-admin/src/pages/Maintenance/Wizard/WizardStep.js
@@ -139,11 +139,6 @@ const WizardStep = ({
R.clamp(0, 100)
)
- const textInput = React.useRef(null)
- React.useEffect(() => {
- textInput.current?.children[0].children[0]?.focus()
- }, [step])
-
return (
@@ -282,7 +277,7 @@ const WizardStep = ({
placeholder={originalCassetteCount.toString()}
name={cassetteField}
className={classes.cashboxBills}
- innerRef={textInput}
+ autoFocus
/>
{cassetteDenomination} {fiatCurrency} bills loaded
From bc8c1eb1ca76539fa1fcc738123623d2e4b49734 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Fri, 7 Jan 2022 19:52:25 +0100
Subject: [PATCH 20/51] fix: remove 'UTC' label
---
.../Machines/MachineComponents/Transactions/Transactions.js | 2 +-
.../src/pages/SessionManagement/SessionManagement.js | 2 +-
new-lamassu-admin/src/pages/Transactions/Transactions.js | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js b/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js
index a5902611..c5e71c95 100644
--- a/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js
+++ b/new-lamassu-admin/src/pages/Machines/MachineComponents/Transactions/Transactions.js
@@ -145,7 +145,7 @@ const Transactions = ({ id }) => {
width: 140
},
{
- header: 'Date (UTC)',
+ header: 'Date',
view: it => formatDate(it.created, timezone, 'yyyy-MM-dd'),
textAlign: 'left',
size: 'sm',
diff --git a/new-lamassu-admin/src/pages/SessionManagement/SessionManagement.js b/new-lamassu-admin/src/pages/SessionManagement/SessionManagement.js
index 13d1fe0c..83457474 100644
--- a/new-lamassu-admin/src/pages/SessionManagement/SessionManagement.js
+++ b/new-lamassu-admin/src/pages/SessionManagement/SessionManagement.js
@@ -79,7 +79,7 @@ const SessionManagement = () => {
}
},
{
- header: 'Expiration date (UTC)',
+ header: 'Expiration date',
width: 290,
textAlign: 'right',
size: 'sm',
diff --git a/new-lamassu-admin/src/pages/Transactions/Transactions.js b/new-lamassu-admin/src/pages/Transactions/Transactions.js
index 016c8b37..009335c3 100644
--- a/new-lamassu-admin/src/pages/Transactions/Transactions.js
+++ b/new-lamassu-admin/src/pages/Transactions/Transactions.js
@@ -222,7 +222,7 @@ const Transactions = () => {
width: 140
},
{
- header: 'Date (UTC)',
+ header: 'Date',
view: it =>
timezone && formatDate(it.created, timezone, 'yyyy-MM-dd HH:mm:ss'),
textAlign: 'right',
From f9d3b4abc0323a9af1b760e24aef3e7886bef991 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Fri, 7 Jan 2022 19:53:05 +0100
Subject: [PATCH 21/51] fix: add scrollbar gutter
---
new-lamassu-admin/src/styling/global/index.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/new-lamassu-admin/src/styling/global/index.js b/new-lamassu-admin/src/styling/global/index.js
index 7c57661e..4e5083ef 100644
--- a/new-lamassu-admin/src/styling/global/index.js
+++ b/new-lamassu-admin/src/styling/global/index.js
@@ -28,7 +28,10 @@ export default {
pointerEvents: 'none'
},
html: {
- height: fill
+ height: fill,
+ '@media screen and (max-height: 900px)': {
+ scrollbarGutter: 'stable'
+ }
},
body: {
width: mainWidth,
From 951a12714dc5e84cdcf32623dee26eadd97e6972 Mon Sep 17 00:00:00 2001
From: Neal
Date: Wed, 12 Jan 2022 11:40:05 -0500
Subject: [PATCH 22/51] chore: update wallet daemons
---
lib/blockchain/common.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/lib/blockchain/common.js b/lib/blockchain/common.js
index ab9c9382..4bcd7a93 100644
--- a/lib/blockchain/common.js
+++ b/lib/blockchain/common.js
@@ -23,18 +23,18 @@ module.exports = {
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',
+ defaultUrl: 'https://bitcoincore.org/bin/bitcoin-core-0.20.1/bitcoin-0.20.1-x86_64-linux-gnu.tar.gz',
+ defaultDir: 'bitcoin-0.20.1/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.14-11a3a350.tar.gz',
- dir: 'geth-linux-amd64-1.10.14-11a3a350'
+ url: 'https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.10.15-8be800ff.tar.gz',
+ dir: 'geth-linux-amd64-1.10.15-8be800ff'
},
ZEC: {
- url: 'https://z.cash/downloads/zcash-4.6.0-linux64-debian-stretch.tar.gz',
- dir: 'zcash-4.6.0/bin'
+ url: 'https://z.cash/downloads/zcash-4.6.0-1-linux64-debian-stretch.tar.gz',
+ dir: 'zcash-4.6.0-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',
From 24bf700eba717548680fd0e3fed8f8edbab9cb6e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Wed, 12 Jan 2022 18:01:47 +0000
Subject: [PATCH 23/51] fix: customer list sorting
---
new-lamassu-admin/src/pages/Customers/Customers.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js
index 09565cb4..70e182f6 100644
--- a/new-lamassu-admin/src/pages/Customers/Customers.js
+++ b/new-lamassu-admin/src/pages/Customers/Customers.js
@@ -80,9 +80,9 @@ const Customers = () => {
const configData = R.path(['config'])(customersResponse) ?? []
const locale = configData && fromNamespace(namespaces.LOCALE, configData)
- const customersData = R.sortWith([R.descend(R.prop('lastActive'))])(
- filteredCustomers ?? []
- )
+ const customersData = R.sortWith([
+ R.descend(it => new Date(R.prop('lastActive', it) ?? '0'))
+ ])(filteredCustomers ?? [])
const onFilterChange = filters => {
const filtersObject = getFiltersObj(filters)
From 5f259dc9d3618d473232d41f667e2b132e60d11c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Wed, 12 Jan 2022 19:01:43 +0000
Subject: [PATCH 24/51] fix: cassettes width
---
.../src/pages/Maintenance/CashCassettes.js | 39 ++++++++++++++++---
1 file changed, 33 insertions(+), 6 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
index 294cb13e..9b78e65d 100644
--- a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
+++ b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
@@ -28,6 +28,30 @@ import Wizard from './Wizard/Wizard'
const useStyles = makeStyles(styles)
+const widthsByNumberOfCassettes = {
+ 2: {
+ machine: 250,
+ cashbox: 260,
+ cassette: 300,
+ cassetteGraph: 80,
+ editWidth: 90
+ },
+ 3: {
+ machine: 220,
+ cashbox: 215,
+ cassette: 225,
+ cassetteGraph: 60,
+ editWidth: 90
+ },
+ 4: {
+ machine: 190,
+ cashbox: 180,
+ cassette: 185,
+ cassetteGraph: 50,
+ editWidth: 90
+ }
+}
+
const ValidationSchema = Yup.object().shape({
name: Yup.string().required(),
cashbox: Yup.number()
@@ -201,14 +225,14 @@ const CashCassettes = () => {
{
name: 'name',
header: 'Machine',
- width: 184,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes].machine,
view: name => <>{name}>,
input: ({ field: { value: name } }) => <>{name}>
},
{
name: 'cashbox',
header: 'Cash box',
- width: maxNumberOfCassettes > 2 ? 140 : 280,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes].cashbox,
view: (value, { id }) => (
{
elements.push({
name: `cassette${it}`,
header: `Cassette ${it}`,
- width: (maxNumberOfCassettes > 2 ? 560 : 650) / maxNumberOfCassettes,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes].cassette,
stripe: true,
doubleHeader: 'Cash-out',
view: (value, { id }) => (
@@ -238,7 +262,9 @@ const CashCassettes = () => {
denomination={getCashoutSettings(id)?.[`cassette${it}`]}
currency={{ code: fiatCurrency }}
notes={value}
- width={50}
+ width={
+ widthsByNumberOfCassettes[maxNumberOfCassettes].cassetteGraph
+ }
threshold={
fillingPercentageSettings[`fillingPercentageCassette${it}`]
}
@@ -248,7 +274,7 @@ const CashCassettes = () => {
input: CashCassetteInput,
inputProps: {
decimalPlaces: 0,
- width: 50,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes].cassetteGraph,
inputClassName: classes.cashbox
}
})
@@ -260,7 +286,8 @@ const CashCassettes = () => {
elements.push({
name: 'edit',
header: 'Edit',
- width: 87,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes].editWidth,
+ textAlign: 'center',
view: (value, { id }) => {
return (
Date: Wed, 12 Jan 2022 23:48:43 +0000
Subject: [PATCH 25/51] fix: stop writing to database on null data
---
lib/routes/customerRoutes.js | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/lib/routes/customerRoutes.js b/lib/routes/customerRoutes.js
index de4e356d..8b0e51d6 100644
--- a/lib/routes/customerRoutes.js
+++ b/lib/routes/customerRoutes.js
@@ -20,8 +20,13 @@ const machineLoader = require('../machine-loader')
const { loadLatestConfig } = require('../new-settings-loader')
const customInfoRequestQueries = require('../new-admin/services/customInfoRequests')
-function updateCustomerCustomInfoRequest (customerId, dataToSave, req, res) {
- return customInfoRequestQueries.setCustomerData(customerId, dataToSave.info_request_id, dataToSave)
+function updateCustomerCustomInfoRequest (customerId, patch, req, res) {
+ if (_.isNil(patch.data)) {
+ return customers.getById(customerId)
+ .then(customer => respond(req, res, { customer }))
+ }
+
+ return customInfoRequestQueries.setCustomerData(customerId, patch.infoRequestId, patch)
.then(() => customers.getById(customerId))
.then(customer => respond(req, res, { customer }))
}
@@ -35,7 +40,7 @@ function updateCustomer (req, res, next) {
const compatTriggers = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
if (patch.customRequestPatch) {
- return updateCustomerCustomInfoRequest(id, patch.dataToSave, req, res).catch(next)
+ return updateCustomerCustomInfoRequest(id, patch.customRequestPatch, req, res).catch(next)
}
customers.getById(id)
From 94eed283cb63eb990a7afe01df1e4d27d09efa02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Wed, 15 Sep 2021 14:42:46 +0100
Subject: [PATCH 26/51] feat: add customer creation modal
---
.../graphql/resolvers/customer.resolver.js | 3 +-
lib/new-admin/graphql/types/customer.type.js | 1 +
new-lamassu-admin/package-lock.json | 5 +
new-lamassu-admin/package.json | 1 +
.../src/components/layout/TitleSection.js | 7 +-
.../src/pages/Customers/Customers.js | 35 +++++-
.../src/pages/Customers/CustomersList.js | 2 +-
.../components/CreateCustomerModal.js | 108 ++++++++++++++++++
8 files changed, 153 insertions(+), 9 deletions(-)
create mode 100644 new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js
diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js
index d3563669..58461fd8 100644
--- a/lib/new-admin/graphql/resolvers/customer.resolver.js
+++ b/lib/new-admin/graphql/resolvers/customer.resolver.js
@@ -49,7 +49,8 @@ const resolvers = {
},
deleteCustomerNote: (...[, { noteId }]) => {
return customerNotes.deleteCustomerNote(noteId)
- }
+ },
+ createCustomer: (...[, { phoneNumber }]) => customers.add({ phone: phoneNumber })
}
}
diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js
index bdbf3a94..ce8cb3cb 100644
--- a/lib/new-admin/graphql/types/customer.type.js
+++ b/lib/new-admin/graphql/types/customer.type.js
@@ -103,6 +103,7 @@ const typeDef = gql`
createCustomerNote(customerId: ID!, title: String!, content: String!): Boolean @auth
editCustomerNote(noteId: ID!, newContent: String!): Boolean @auth
deleteCustomerNote(noteId: ID!): Boolean @auth
+ createCustomer(phoneNumber: String): Customer @auth
}
`
diff --git a/new-lamassu-admin/package-lock.json b/new-lamassu-admin/package-lock.json
index e64cc12a..b54297ef 100644
--- a/new-lamassu-admin/package-lock.json
+++ b/new-lamassu-admin/package-lock.json
@@ -13870,6 +13870,11 @@
"delegate": "^3.1.2"
}
},
+ "google-libphonenumber": {
+ "version": "3.2.22",
+ "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.22.tgz",
+ "integrity": "sha512-lzEllxWc05n/HEv75SsDrA7zdEVvQzTZimItZm/TZ5XBs7cmx2NJmSlA5I0kZbdKNu8GFETBhSpo+SOhx0JslA=="
+ },
"graceful-fs": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.5.tgz",
diff --git a/new-lamassu-admin/package.json b/new-lamassu-admin/package.json
index bd97a32c..ab5f4f1c 100644
--- a/new-lamassu-admin/package.json
+++ b/new-lamassu-admin/package.json
@@ -26,6 +26,7 @@
"downshift": "3.3.4",
"file-saver": "2.0.2",
"formik": "2.2.0",
+ "google-libphonenumber": "^3.2.22",
"graphql": "^14.5.8",
"graphql-tag": "^2.10.3",
"jss-plugin-extend": "^10.0.0",
diff --git a/new-lamassu-admin/src/components/layout/TitleSection.js b/new-lamassu-admin/src/components/layout/TitleSection.js
index 6858ed5f..f0777103 100644
--- a/new-lamassu-admin/src/components/layout/TitleSection.js
+++ b/new-lamassu-admin/src/components/layout/TitleSection.js
@@ -19,14 +19,14 @@ const TitleSection = ({
buttons = [],
children,
appendix,
- appendixClassName
+ appendixRight
}) => {
const classes = useStyles()
return (
{title}
- {appendix &&
{appendix}
}
+ {!!appendix && appendix}
{error && (
Failed to save
)}
@@ -46,13 +46,14 @@ const TitleSection = ({
>
)}
-
+
{(labels ?? []).map(({ icon, label }, idx) => (
{icon}
{label}
))}
+ {appendixRight}
{children}
diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js
index 70e182f6..05c770c3 100644
--- a/new-lamassu-admin/src/pages/Customers/Customers.js
+++ b/new-lamassu-admin/src/pages/Customers/Customers.js
@@ -1,5 +1,5 @@
-import { useQuery } from '@apollo/react-hooks'
-import { makeStyles } from '@material-ui/core/styles'
+import { useQuery, useMutation } from '@apollo/react-hooks'
+import { Box, makeStyles } from '@material-ui/core'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { useState } from 'react'
@@ -7,6 +7,7 @@ import { useHistory } from 'react-router-dom'
import SearchBox from 'src/components/SearchBox'
import SearchFilter from 'src/components/SearchFilter'
+import { Link } from 'src/components/buttons'
import TitleSection from 'src/components/layout/TitleSection'
import baseStyles from 'src/pages/Logs.styles'
import { ReactComponent as TxInIcon } from 'src/styling/icons/direction/cash-in.svg'
@@ -14,6 +15,7 @@ import { ReactComponent as TxOutIcon } from 'src/styling/icons/direction/cash-ou
import { fromNamespace, namespaces } from 'src/utils/config'
import CustomersList from './CustomersList'
+import CreateCustomerModal from './components/CreateCustomerModal'
const GET_CUSTOMER_FILTERS = gql`
query filters {
@@ -49,6 +51,14 @@ const GET_CUSTOMERS = gql`
}
`
+const CREATE_CUSTOMER = gql`
+ mutation createCustomer($phoneNumber: String) {
+ createCustomer(phoneNumber: $phoneNumber) {
+ phone
+ }
+ }
+`
+
const useBaseStyles = makeStyles(baseStyles)
const getFiltersObj = filters =>
@@ -64,6 +74,7 @@ const Customers = () => {
const [filteredCustomers, setFilteredCustomers] = useState([])
const [variables, setVariables] = useState({})
const [filters, setFilters] = useState([])
+ const [showCreationModal, setShowCreationModal] = useState(false)
const {
data: customersResponse,
@@ -78,6 +89,11 @@ const Customers = () => {
GET_CUSTOMER_FILTERS
)
+ const [createNewCustomer] = useMutation(CREATE_CUSTOMER, {
+ onCompleted: () => setShowCreationModal(false),
+ refetchQueries: () => ['configAndCustomers']
+ })
+
const configData = R.path(['config'])(customersResponse) ?? []
const locale = configData && fromNamespace(namespaces.LOCALE, configData)
const customersData = R.sortWith([
@@ -139,7 +155,7 @@ const Customers = () => {
+
{
/>
}
- appendixClassName={baseStyles.buttonsWrapper}
+ appendixRight={
+
+ setShowCreationModal(true)}>
+ Add new user
+
+
+ }
labels={[
{ label: 'Cash-in', icon: },
{ label: 'Cash-out', icon: }
@@ -169,6 +191,11 @@ const Customers = () => {
onClick={handleCustomerClicked}
loading={customerLoading}
/>
+ setShowCreationModal(false)}
+ onSubmit={createNewCustomer}
+ />
>
)
}
diff --git a/new-lamassu-admin/src/pages/Customers/CustomersList.js b/new-lamassu-admin/src/pages/Customers/CustomersList.js
index 43e40c2a..6b9e4167 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomersList.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomersList.js
@@ -19,7 +19,7 @@ const CustomersList = ({ data, locale, onClick, loading }) => {
const elements = [
{
header: 'Phone',
- width: 175,
+ width: 199,
view: it => getFormattedPhone(it.phone, locale.country)
},
{
diff --git a/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js b/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js
new file mode 100644
index 00000000..f36048a6
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js
@@ -0,0 +1,108 @@
+import { makeStyles } from '@material-ui/core/styles'
+import { Field, Form, Formik } from 'formik'
+import { PhoneNumberUtil } from 'google-libphonenumber'
+import React from 'react'
+import * as Yup from 'yup'
+
+import ErrorMessage from 'src/components/ErrorMessage'
+import Modal from 'src/components/Modal'
+import { Button } from 'src/components/buttons'
+import { TextInput } from 'src/components/inputs/formik'
+import { H1 } from 'src/components/typography'
+import { spacer, primaryColor, fontPrimary } from 'src/styling/variables'
+
+const styles = {
+ modalTitle: {
+ marginTop: -5,
+ color: primaryColor,
+ fontFamily: fontPrimary
+ },
+ footer: {
+ display: 'flex',
+ flexDirection: 'row',
+ margin: [['auto', 0, spacer * 3, 0]]
+ },
+ form: {
+ display: 'flex',
+ flexDirection: 'column',
+ height: '100%'
+ },
+ submit: {
+ margin: [['auto', 0, 0, 'auto']]
+ }
+}
+
+const pnUtilInstance = PhoneNumberUtil.getInstance()
+
+const validationSchema = Yup.object().shape({
+ phoneNumber: Yup.string()
+ .required('A phone number is required')
+ .test('is-valid-number', 'That is not a valid phone number', value => {
+ try {
+ const number = pnUtilInstance.parseAndKeepRawInput(value, 'US')
+ return pnUtilInstance.isValidNumber(number)
+ } catch (e) {}
+ })
+})
+
+const initialValues = {
+ phoneNumber: ''
+}
+
+const useStyles = makeStyles(styles)
+
+const getErrorMsg = (formikErrors, formikTouched) => {
+ if (!formikErrors || !formikTouched) return null
+ if (formikErrors.phoneNumber && formikTouched.phoneNumber)
+ return formikErrors.phoneNumber
+ return null
+}
+
+const CreateCustomerModal = ({ showModal, handleClose, onSubmit }) => {
+ const classes = useStyles()
+
+ return (
+
+ {
+ onSubmit({
+ variables: { phoneNumber: values.phoneNumber }
+ })
+ }}>
+ {({ errors, touched }) => (
+
+ )}
+
+
+ )
+}
+
+export default CreateCustomerModal
From 18d5d7372f4143710565e1e2785e362955559566 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Fri, 17 Sep 2021 19:18:18 +0100
Subject: [PATCH 27/51] fix: add phone number input validation
---
.../src/pages/Customers/Customers.js | 1 +
.../components/CreateCustomerModal.js | 59 ++++++++++++++-----
2 files changed, 46 insertions(+), 14 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js
index 05c770c3..1875d44d 100644
--- a/new-lamassu-admin/src/pages/Customers/Customers.js
+++ b/new-lamassu-admin/src/pages/Customers/Customers.js
@@ -194,6 +194,7 @@ const Customers = () => {
setShowCreationModal(false)}
+ locale={locale}
onSubmit={createNewCustomer}
/>
>
diff --git a/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js b/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js
index f36048a6..984d9d64 100644
--- a/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js
+++ b/new-lamassu-admin/src/pages/Customers/components/CreateCustomerModal.js
@@ -1,6 +1,7 @@
import { makeStyles } from '@material-ui/core/styles'
import { Field, Form, Formik } from 'formik'
-import { PhoneNumberUtil } from 'google-libphonenumber'
+import { PhoneNumberFormat, PhoneNumberUtil } from 'google-libphonenumber'
+import * as R from 'ramda'
import React from 'react'
import * as Yup from 'yup'
@@ -34,16 +35,36 @@ const styles = {
const pnUtilInstance = PhoneNumberUtil.getInstance()
-const validationSchema = Yup.object().shape({
- phoneNumber: Yup.string()
- .required('A phone number is required')
- .test('is-valid-number', 'That is not a valid phone number', value => {
- try {
- const number = pnUtilInstance.parseAndKeepRawInput(value, 'US')
- return pnUtilInstance.isValidNumber(number)
- } catch (e) {}
- })
-})
+const getValidationSchema = countryCodes =>
+ Yup.object().shape({
+ phoneNumber: Yup.string()
+ .required('A phone number is required')
+ .test('is-valid-number', 'That is not a valid phone number', value => {
+ try {
+ const validMap = R.map(it => {
+ const number = pnUtilInstance.parseAndKeepRawInput(value, it)
+ return pnUtilInstance.isValidNumber(number)
+ }, countryCodes)
+
+ return R.any(it => it === true, validMap)
+ } catch (e) {}
+ })
+ .trim()
+ })
+
+const formatPhoneNumber = (countryCodes, numberStr) => {
+ const matchedCountry = R.find(it => {
+ const number = pnUtilInstance.parseAndKeepRawInput(numberStr, it)
+ return pnUtilInstance.isValidNumber(number)
+ }, countryCodes)
+
+ const matchedNumber = pnUtilInstance.parseAndKeepRawInput(
+ numberStr,
+ matchedCountry
+ )
+
+ return pnUtilInstance.format(matchedNumber, PhoneNumberFormat.E164)
+}
const initialValues = {
phoneNumber: ''
@@ -58,9 +79,14 @@ const getErrorMsg = (formikErrors, formikTouched) => {
return null
}
-const CreateCustomerModal = ({ showModal, handleClose, onSubmit }) => {
+const CreateCustomerModal = ({ showModal, handleClose, onSubmit, locale }) => {
const classes = useStyles()
+ const possibleCountries = R.append(
+ locale?.country,
+ R.map(it => it.country, locale?.overrides ?? [])
+ )
+
return (
{
handleClose={handleClose}
open={showModal}>
{
onSubmit({
- variables: { phoneNumber: values.phoneNumber }
+ variables: {
+ phoneNumber: formatPhoneNumber(
+ possibleCountries,
+ values.phoneNumber
+ )
+ }
})
}}>
{({ errors, touched }) => (
From cf968aeaa32f90d208f28b68401eae649053668e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Fri, 12 Nov 2021 15:58:44 +0000
Subject: [PATCH 28/51] fix: customer list column widths
---
new-lamassu-admin/src/pages/Customers/CustomersList.js | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Customers/CustomersList.js b/new-lamassu-admin/src/pages/Customers/CustomersList.js
index 6b9e4167..2a3cca6a 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomersList.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomersList.js
@@ -24,31 +24,31 @@ const CustomersList = ({ data, locale, onClick, loading }) => {
},
{
header: 'Name',
- width: 247,
+ width: 241,
view: getName
},
{
header: 'Total TXs',
- width: 130,
+ width: 126,
textAlign: 'right',
view: it => `${Number.parseInt(it.totalTxs)}`
},
{
header: 'Total spent',
- width: 155,
+ width: 152,
textAlign: 'right',
view: it =>
`${Number.parseFloat(it.totalSpent)} ${it.lastTxFiatCode ?? ''}`
},
{
header: 'Last active',
- width: 137,
+ width: 133,
view: it =>
(it.lastActive && format('yyyy-MM-dd', new Date(it.lastActive))) ?? ''
},
{
header: 'Last transaction',
- width: 165,
+ width: 161,
textAlign: 'right',
view: it => {
const hasLastTx = !R.isNil(it.lastTxFiatCode)
From bd0701236c24890f5e16e6683cd21d7a32ca174b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Oliveira?=
Date: Thu, 13 Jan 2022 15:30:31 +0000
Subject: [PATCH 29/51] feat: enable custom entries and custom information
requirements
---
lib/customers.js | 1 +
lib/new-admin/graphql/types/customer.type.js | 18 +--
.../src/pages/Customers/CustomerData.js | 139 ++++++------------
.../src/pages/Customers/CustomerProfile.js | 47 +++++-
.../src/pages/Customers/Wizard.js | 57 +++++--
.../Customers/components/EditableCard.js | 6 +-
.../src/pages/Customers/helper.js | 114 ++++++++++++--
.../Graphs/RefScatterplot.js | 2 +-
.../src/pages/Triggers/helper.js | 2 +-
9 files changed, 257 insertions(+), 129 deletions(-)
diff --git a/lib/customers.js b/lib/customers.js
index e96155f9..0f765bc9 100644
--- a/lib/customers.js
+++ b/lib/customers.js
@@ -993,6 +993,7 @@ function addCustomField (customerId, label, value) {
}
})
)
+ .then(res => !_.isNil(res))
}
function saveCustomField (customerId, fieldId, newValue) {
diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js
index bdbf3a94..765f65e1 100644
--- a/lib/new-admin/graphql/types/customer.type.js
+++ b/lib/new-admin/graphql/types/customer.type.js
@@ -1,12 +1,6 @@
const { gql } = require('apollo-server-express')
const typeDef = gql`
- type CustomerCustomField {
- id: ID
- label: String
- value: String
- }
-
type Customer {
id: ID!
authorizedOverride: String
@@ -86,6 +80,12 @@ const typeDef = gql`
content: String
}
+ type CustomerCustomField {
+ id: ID
+ label: String
+ value: String
+ }
+
type Query {
customers(phone: String, name: String, address: String, id: String): [Customer] @auth
customer(customerId: ID!): Customer @auth
@@ -94,9 +94,9 @@ const typeDef = gql`
type Mutation {
setCustomer(customerId: ID!, customerInput: CustomerInput): Customer @auth
- addCustomField(customerId: ID!, label: String!, value: String!): CustomerCustomField @auth
- saveCustomField(customerId: ID!, fieldId: ID!, value: String!): CustomerCustomField @auth
- removeCustomField(customerId: ID!, fieldId: ID!): CustomerCustomField @auth
+ addCustomField(customerId: ID!, label: String!, value: String!): Boolean @auth
+ saveCustomField(customerId: ID!, fieldId: ID!, value: String!): Boolean @auth
+ removeCustomField(customerId: ID!, fieldId: ID!): Boolean @auth
editCustomer(customerId: ID!, customerEdit: CustomerEdit): Customer @auth
deleteEditedData(customerId: ID!, customerEdit: CustomerEdit): Customer @auth
replacePhoto(customerId: ID!, photoType: String, newPhoto: UploadGQL): Customer @auth
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js
index 18a53cac..a5346a0e 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerData.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js
@@ -1,6 +1,6 @@
import Grid from '@material-ui/core/Grid'
import { makeStyles } from '@material-ui/core/styles'
-import { parse, format, isValid } from 'date-fns/fp'
+import { parse, format } from 'date-fns/fp'
import _ from 'lodash/fp'
import * as R from 'ramda'
import { useState, React } from 'react'
@@ -26,6 +26,7 @@ import { URI } from 'src/utils/apollo'
import styles from './CustomerData.styles.js'
import { EditableCard } from './components'
+import { customerDataElements, customerDataschemas } from './helper.js'
const useStyles = makeStyles(styles)
@@ -84,8 +85,8 @@ const CustomerData = ({
R.compose(R.toLower, R.path(['customInfoRequest', 'customRequest', 'name']))
)
- const customEntries = null // get customer custom entries
- const customRequirements = [] // get customer custom requirements
+ const customFields = []
+ const customRequirements = []
const customInfoRequests = sortByName(
R.path(['customInfoRequests'])(customer) ?? []
)
@@ -94,85 +95,6 @@ const CustomerData = ({
const getVisibleCards = _.filter(elem => elem.isAvailable)
- const schemas = {
- idScan: Yup.object().shape({
- firstName: Yup.string().required(),
- lastName: Yup.string().required(),
- documentNumber: Yup.string().required(),
- dateOfBirth: Yup.string()
- .test({
- test: val => isValid(parse(new Date(), 'yyyy-MM-dd', val))
- })
- .required(),
- gender: Yup.string().required(),
- country: Yup.string().required(),
- expirationDate: Yup.string()
- .test({
- test: val => isValid(parse(new Date(), 'yyyy-MM-dd', val))
- })
- .required()
- }),
- usSsn: Yup.object().shape({
- usSsn: Yup.string().required()
- }),
- idCardPhoto: Yup.object().shape({
- idCardPhoto: Yup.mixed().required()
- }),
- frontCamera: Yup.object().shape({
- frontCamera: Yup.mixed().required()
- })
- }
-
- const idScanElements = [
- {
- name: 'firstName',
- label: 'First name',
- component: TextInput
- },
- {
- name: 'documentNumber',
- label: 'ID number',
- component: TextInput
- },
- {
- name: 'dateOfBirth',
- label: 'Birthdate',
- component: TextInput
- },
- {
- name: 'gender',
- label: 'Gender',
- component: TextInput
- },
- {
- name: 'lastName',
- label: 'Last name',
- component: TextInput
- },
- {
- name: 'expirationDate',
- label: 'Expiration Date',
- component: TextInput
- },
- {
- name: 'country',
- label: 'Country',
- component: TextInput
- }
- ]
-
- const usSsnElements = [
- {
- name: 'usSsn',
- label: 'US SSN',
- component: TextInput,
- size: 190
- }
- ]
-
- const idCardPhotoElements = [{ name: 'idCardPhoto' }]
- const frontCameraElements = [{ name: 'frontCamera' }]
-
const initialValues = {
idScan: {
firstName: R.path(['firstName'])(idData) ?? '',
@@ -214,7 +136,7 @@ const CustomerData = ({
const cards = [
{
- fields: idScanElements,
+ fields: customerDataElements.idScanElements,
title: 'ID Scan',
titleIcon: ,
state: R.path(['idCardDataOverride'])(customer),
@@ -226,7 +148,7 @@ const CustomerData = ({
editCustomer({
idCardData: _.merge(idData, formatDates(values))
}),
- validationSchema: schemas.idScan,
+ validationSchema: customerDataschemas.idScan,
initialValues: initialValues.idScan,
isAvailable: !_.isNil(idData)
},
@@ -257,7 +179,7 @@ const CustomerData = ({
isAvailable: !_.isNil(sanctions)
},
{
- fields: frontCameraElements,
+ fields: customerDataElements.frontCameraElements,
title: 'Front facing camera',
titleIcon: ,
state: R.path(['frontCameraOverride'])(customer),
@@ -279,12 +201,12 @@ const CustomerData = ({
/>
) : null,
hasImage: true,
- validationSchema: schemas.frontCamera,
+ validationSchema: customerDataschemas.frontCamera,
initialValues: initialValues.frontCamera,
isAvailable: !_.isNil(customer.frontCameraPath)
},
{
- fields: idCardPhotoElements,
+ fields: customerDataElements.idCardPhotoElements,
title: 'ID card image',
titleIcon: ,
state: R.path(['idCardPhotoOverride'])(customer),
@@ -304,12 +226,12 @@ const CustomerData = ({
/>
) : null,
hasImage: true,
- validationSchema: schemas.idCardPhoto,
+ validationSchema: customerDataschemas.idCardPhoto,
initialValues: initialValues.idCardPhoto,
isAvailable: !_.isNil(customer.idCardPhotoPath)
},
{
- fields: usSsnElements,
+ fields: customerDataElements.usSsnElements,
title: 'US SSN',
titleIcon: ,
state: R.path(['usSsnOverride'])(customer),
@@ -317,7 +239,7 @@ const CustomerData = ({
reject: () => updateCustomer({ usSsnOverride: OVERRIDE_REJECTED }),
save: values => editCustomer({ usSsn: values.usSsn }),
deleteEditedData: () => deleteEditedData({ usSsn: null }),
- validationSchema: schemas.usSsn,
+ validationSchema: customerDataschemas.usSsn,
initialValues: initialValues.usSsn,
isAvailable: !_.isNil(customer.usSsn)
}
@@ -374,6 +296,29 @@ const CustomerData = ({
})
}, customInfoRequests)
+ R.forEach(it => {
+ customFields.push({
+ fields: [
+ {
+ name: it.label,
+ label: it.label,
+ value: it.value ?? '',
+ component: TextInput
+ }
+ ],
+ title: it.label,
+ titleIcon: ,
+ save: () => {},
+ deleteEditedData: () => {},
+ validationSchema: Yup.object().shape({
+ [it.label]: Yup.string()
+ }),
+ initialValues: {
+ [it.label]: it.value ?? ''
+ }
+ })
+ }, R.path(['customFields'])(customer) ?? [])
+
const editableCard = (
{
title,
@@ -444,9 +389,21 @@ const CustomerData = ({
)}
- {customEntries && (
+ {!_.isEmpty(customFields) && (
Custom data entry
+
+
+ {customFields.map((elem, idx) => {
+ return isEven(idx) ? editableCard(elem, idx) : null
+ })}
+
+
+ {customFields.map((elem, idx) => {
+ return !isEven(idx) ? editableCard(elem, idx) : null
+ })}
+
+
)}
{!R.isEmpty(customRequirements) && (
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
index 91b076ee..d83960d3 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
@@ -236,6 +236,21 @@ const GET_DATA = gql`
}
`
+const SET_CUSTOM_ENTRY = gql`
+ mutation addCustomField($customerId: ID!, $label: String!, $value: String!) {
+ addCustomField(customerId: $customerId, label: $label, value: $value)
+ }
+`
+
+const GET_ACTIVE_CUSTOM_REQUESTS = gql`
+ query customInfoRequests($onlyEnabled: Boolean) {
+ customInfoRequests(onlyEnabled: $onlyEnabled) {
+ id
+ customRequest
+ }
+ }
+`
+
const CustomerProfile = memo(() => {
const history = useHistory()
@@ -255,6 +270,16 @@ const CustomerProfile = memo(() => {
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
+ const { data: activeCustomRequests } = useQuery(GET_ACTIVE_CUSTOM_REQUESTS, {
+ variables: {
+ onlyEnabled: true
+ }
+ })
+
+ const [setCustomEntry] = useMutation(SET_CUSTOM_ENTRY, {
+ onCompleted: () => getCustomer()
+ })
+
const [replaceCustomerPhoto] = useMutation(REPLACE_CUSTOMER_PHOTO, {
onCompleted: () => getCustomer()
})
@@ -294,6 +319,17 @@ const CustomerProfile = memo(() => {
onCompleted: () => getCustomer()
})
+ const saveCustomEntry = it => {
+ setCustomEntry({
+ variables: {
+ customerId,
+ label: it.title,
+ value: it.data
+ }
+ })
+ setWizard(null)
+ }
+
const updateCustomer = it =>
setCustomer({
variables: {
@@ -385,6 +421,12 @@ const CustomerProfile = memo(() => {
const timezone = R.path(['config', 'locale_timezone'], configResponse)
+ const customRequirementOptions =
+ activeCustomRequests?.customInfoRequests?.map(it => ({
+ value: it.id,
+ display: it.customRequest.name
+ })) ?? []
+
const classes = useStyles()
return (
@@ -544,8 +586,11 @@ const CustomerProfile = memo(() => {
{wizard && (
{}}
+ save={saveCustomEntry}
+ addPhoto={replacePhoto}
+ addCustomerData={editCustomer}
onClose={() => setWizard(null)}
+ customRequirementOptions={customRequirementOptions}
/>
)}
diff --git a/new-lamassu-admin/src/pages/Customers/Wizard.js b/new-lamassu-admin/src/pages/Customers/Wizard.js
index 87bebaa8..16db1647 100644
--- a/new-lamassu-admin/src/pages/Customers/Wizard.js
+++ b/new-lamassu-admin/src/pages/Customers/Wizard.js
@@ -1,5 +1,5 @@
import { makeStyles } from '@material-ui/core'
-import { Form, Formik } from 'formik'
+import { Form, Formik, Field } from 'formik'
import * as R from 'ramda'
import React, { useState, Fragment } from 'react'
@@ -7,6 +7,7 @@ import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import Stepper from 'src/components/Stepper'
import { Button } from 'src/components/buttons'
+import { Dropdown } from 'src/components/inputs/formik'
import { comet } from 'src/styling/variables'
import { entryType, customElements } from './helper'
@@ -41,6 +42,10 @@ const styles = {
margin: [[0, 4, 0, 2]],
borderBottom: `1px solid ${comet}`,
display: 'inline-block'
+ },
+ dropdownField: {
+ marginTop: 16,
+ minWidth: 155
}
}
@@ -57,7 +62,14 @@ const getStep = (step, selectedValues) => {
}
}
-const Wizard = ({ onClose, save, error }) => {
+const Wizard = ({
+ onClose,
+ save,
+ error,
+ customRequirementOptions,
+ addCustomerData,
+ addPhoto
+}) => {
const classes = useStyles()
const [selectedValues, setSelectedValues] = useState(null)
@@ -66,6 +78,7 @@ const Wizard = ({ onClose, save, error }) => {
step: 1
})
+ const isCustom = values => values?.requirement === 'custom'
const isLastStep = step === LAST_STEP
const stepOptions = getStep(step, selectedValues)
@@ -103,18 +116,34 @@ const Wizard = ({ onClose, save, error }) => {
onSubmit={onContinue}
initialValues={stepOptions.initialValues}
validationSchema={stepOptions.schema}>
-
+ {({ values }) => (
+
+ )}
>
diff --git a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js
index 0239db8a..65507e91 100644
--- a/new-lamassu-admin/src/pages/Customers/components/EditableCard.js
+++ b/new-lamassu-admin/src/pages/Customers/components/EditableCard.js
@@ -150,7 +150,7 @@ const EditableCard = ({
{title}
- {state && (
+ {state && authorize && (
@@ -279,7 +279,7 @@ const EditableCard = ({
Cancel
- {authorized.label !== 'Accepted' && (
+ {authorize && authorized.label !== 'Accepted' && (
)}
- {authorized.label !== 'Rejected' && (
+ {authorize && authorized.label !== 'Rejected' && (
{
) ?? ''}`.trim()
}
+// Manual Entry Wizard
+
const entryOptions = [
{ display: 'Custom entry', code: 'custom' },
{ display: 'Populate existing requirement', code: 'requirement' }
]
const dataOptions = [
- { display: 'Text', code: 'text' },
- { display: 'File', code: 'file' },
- { display: 'Image', code: 'image' }
+ { display: 'Text', code: 'text' }
+ // TODO: Requires backend modifications to support File and Image
+ // { display: 'File', code: 'file' },
+ // { display: 'Image', code: 'image' }
]
const requirementOptions = [
- { display: 'Birthdate', code: 'birthdate' },
{ display: 'ID card image', code: 'idCardPhoto' },
{ display: 'ID data', code: 'idCardData' },
- { display: 'Customer camera', code: 'facephoto' },
- { display: 'US SSN', code: 'usSsn' }
+ { display: 'US SSN', code: 'usSsn' },
+ { display: 'Customer camera', code: 'facephoto' }
]
const customTextOptions = [
@@ -108,7 +111,7 @@ const customTextSchema = Yup.object().shape({
data: Yup.string().required()
})
-const EntryType = () => {
+const EntryType = ({ hasCustomRequirementOptions }) => {
const classes = useStyles()
const { values } = useFormikContext()
@@ -154,7 +157,17 @@ const EntryType = () => {
isValid(parse(new Date(), 'yyyy-MM-dd', val))
+ })
+ .required(),
+ gender: Yup.string().required(),
+ country: Yup.string().required(),
+ expirationDate: Yup.string()
+ .test({
+ test: val => isValid(parse(new Date(), 'yyyy-MM-dd', val))
+ })
+ .required()
+ }),
+ usSsn: Yup.object().shape({
+ usSsn: Yup.string().required()
+ }),
+ idCardPhoto: Yup.object().shape({
+ idCardPhoto: Yup.mixed().required()
+ }),
+ frontCamera: Yup.object().shape({
+ frontCamera: Yup.mixed().required()
+ })
+}
+
const mapKeys = pair => {
const [key, value] = pair
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
@@ -245,5 +339,7 @@ export {
getName,
entryType,
customElements,
- formatPhotosData
+ formatPhotosData,
+ customerDataElements,
+ customerDataschemas
}
diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js
index f2eb4e78..ef092185 100644
--- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js
+++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/Graphs/RefScatterplot.js
@@ -62,7 +62,7 @@ const Graph = ({ data, timeFrame, timezone }) => {
[]
)
- const filterDay = useMemo(
+ const filterDay = useCallback(
x => (timeFrame === 'day' ? x.getUTCHours() === 0 : x.getUTCDate() === 1),
[timeFrame]
)
diff --git a/new-lamassu-admin/src/pages/Triggers/helper.js b/new-lamassu-admin/src/pages/Triggers/helper.js
index fcf91e71..20a11664 100644
--- a/new-lamassu-admin/src/pages/Triggers/helper.js
+++ b/new-lamassu-admin/src/pages/Triggers/helper.js
@@ -554,7 +554,7 @@ const Requirement = () => {
}
const options = enableCustomRequirement
? [...requirementOptions, customInfoOption]
- : [...requirementOptions, { ...customInfoOption, disabled: true }]
+ : [...requirementOptions]
const titleClass = {
[classes.error]:
(!!errors.requirement && !isSuspend) || (isSuspend && hasRequirementError)
From 70b34458846dab339d207232c1dd42e4511eb2dc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Oliveira?=
Date: Thu, 13 Jan 2022 16:19:48 +0000
Subject: [PATCH 30/51] fix: subpage buttons missing
---
.../src/pages/Maintenance/CashCassettes.js | 14 ++++++++------
new-lamassu-admin/src/pages/Wallet/Wallet.js | 14 ++++++++------
2 files changed, 16 insertions(+), 12 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
index 294cb13e..9ae0a6bd 100644
--- a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
+++ b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
@@ -279,12 +279,14 @@ const CashCassettes = () => {
<>
{!showHistory && (
diff --git a/new-lamassu-admin/src/pages/Wallet/Wallet.js b/new-lamassu-admin/src/pages/Wallet/Wallet.js
index b80ea805..1fff8153 100644
--- a/new-lamassu-admin/src/pages/Wallet/Wallet.js
+++ b/new-lamassu-admin/src/pages/Wallet/Wallet.js
@@ -140,12 +140,14 @@ const Wallet = ({ name: SCREEN_KEY }) => {
Fee discount
From 0d88e6eba2c47d157fc877fd5e47e7e77886b7e4 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Thu, 13 Jan 2022 18:25:33 +0100
Subject: [PATCH 31/51] fix: date parsing
---
new-lamassu-admin/src/pages/Transactions/DetailsCard.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
index 334ee3a5..e33ca0a7 100644
--- a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
+++ b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
@@ -1,7 +1,7 @@
import { useLazyQuery, useMutation } from '@apollo/react-hooks'
import { makeStyles, Box } from '@material-ui/core'
import BigNumber from 'bignumber.js'
-import { add, differenceInYears, format, sub } from 'date-fns/fp'
+import { add, differenceInYears, format, sub, parse } from 'date-fns/fp'
import FileSaver from 'file-saver'
import gql from 'graphql-tag'
import JSZip from 'jszip'
@@ -123,9 +123,8 @@ const DetailsRow = ({ it: tx, timezone }) => {
age: differenceInYears(tx.customerIdCardData.dateOfBirth, new Date()),
country: tx.customerIdCardData.country,
idCardNumber: tx.customerIdCardData.documentNumber,
- idCardExpirationDate: format(
- 'dd-MM-yyyy',
- tx.customerIdCardData.expirationDate
+ idCardExpirationDate: format('yyyy-MM-dd')(
+ parse(new Date(), 'yyyyMMdd', tx.customerIdCardData.expirationDate)
)
}
From 681b76739f6dc6efe859e713b5eb3e84ea60cee7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Thu, 13 Jan 2022 17:42:22 +0000
Subject: [PATCH 32/51] fix: initialize empty cassettes in case of undefined
values
---
lib/bill-math.js | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/bill-math.js b/lib/bill-math.js
index d4801c0f..fbb3ee99 100644
--- a/lib/bill-math.js
+++ b/lib/bill-math.js
@@ -73,8 +73,9 @@ function unmergeCassettes(cassettes, output) {
}
function makeChangeDuo(cassettes, amount) {
- const small = cassettes[0]
- const large = cassettes[1]
+ // Initialize empty cassettes in case of undefined, due to same denomination across all cassettes results in a single merged cassette
+ const small = cassettes[0] ?? { denomination: 0, count: 0 }
+ const large = cassettes[1] ?? { denomination: 0, count: 0 }
const largeDenom = large.denomination
const smallDenom = small.denomination
From 7b069c3e7b9ccee2edb60e4cd24ffe52403645d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Fri, 14 Jan 2022 07:08:20 +0000
Subject: [PATCH 33/51] fix: revert header changes fix: null pointer check
---
new-lamassu-admin/src/components/layout/Header.js | 2 +-
.../src/pages/Maintenance/CashCassettes.js | 12 ++++++------
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/new-lamassu-admin/src/components/layout/Header.js b/new-lamassu-admin/src/components/layout/Header.js
index 12e57ae5..0c62549a 100644
--- a/new-lamassu-admin/src/components/layout/Header.js
+++ b/new-lamassu-admin/src/components/layout/Header.js
@@ -132,7 +132,7 @@ const Header = memo(({ tree, user }) => {
return (
{
if (!match) return false
setActive(it)
diff --git a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
index 9b78e65d..7cec8ec7 100644
--- a/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
+++ b/new-lamassu-admin/src/pages/Maintenance/CashCassettes.js
@@ -225,14 +225,14 @@ const CashCassettes = () => {
{
name: 'name',
header: 'Machine',
- width: widthsByNumberOfCassettes[maxNumberOfCassettes].machine,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.machine,
view: name => <>{name}>,
input: ({ field: { value: name } }) => <>{name}>
},
{
name: 'cashbox',
header: 'Cash box',
- width: widthsByNumberOfCassettes[maxNumberOfCassettes].cashbox,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.cashbox,
view: (value, { id }) => (
{
elements.push({
name: `cassette${it}`,
header: `Cassette ${it}`,
- width: widthsByNumberOfCassettes[maxNumberOfCassettes].cassette,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.cassette,
stripe: true,
doubleHeader: 'Cash-out',
view: (value, { id }) => (
@@ -263,7 +263,7 @@ const CashCassettes = () => {
currency={{ code: fiatCurrency }}
notes={value}
width={
- widthsByNumberOfCassettes[maxNumberOfCassettes].cassetteGraph
+ widthsByNumberOfCassettes[maxNumberOfCassettes]?.cassetteGraph
}
threshold={
fillingPercentageSettings[`fillingPercentageCassette${it}`]
@@ -274,7 +274,7 @@ const CashCassettes = () => {
input: CashCassetteInput,
inputProps: {
decimalPlaces: 0,
- width: widthsByNumberOfCassettes[maxNumberOfCassettes].cassetteGraph,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.cassetteGraph,
inputClassName: classes.cashbox
}
})
@@ -286,7 +286,7 @@ const CashCassettes = () => {
elements.push({
name: 'edit',
header: 'Edit',
- width: widthsByNumberOfCassettes[maxNumberOfCassettes].editWidth,
+ width: widthsByNumberOfCassettes[maxNumberOfCassettes]?.editWidth,
textAlign: 'center',
view: (value, { id }) => {
return (
From 88637c4776af8dda572702c8150b282335de655f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Fri, 14 Jan 2022 19:38:17 +0000
Subject: [PATCH 34/51] fix: improve UX when creating a trigger using custom
requirements
---
.../src/pages/Triggers/TriggerView.js | 4 +++
.../src/pages/Triggers/Wizard.js | 8 +++---
.../src/pages/Triggers/helper.js | 26 ++++---------------
3 files changed, 13 insertions(+), 25 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Triggers/TriggerView.js b/new-lamassu-admin/src/pages/Triggers/TriggerView.js
index 52256f55..93bbdc87 100644
--- a/new-lamassu-admin/src/pages/Triggers/TriggerView.js
+++ b/new-lamassu-admin/src/pages/Triggers/TriggerView.js
@@ -75,6 +75,10 @@ const TriggerView = ({
error={error?.message}
save={add}
onClose={toggleWizard}
+ customInfoRequests={R.filter(
+ it => it.enabled === true,
+ customInfoRequests
+ )}
/>
)}
{R.isEmpty(triggers) && (
diff --git a/new-lamassu-admin/src/pages/Triggers/Wizard.js b/new-lamassu-admin/src/pages/Triggers/Wizard.js
index 44eb7875..dfc5fd03 100644
--- a/new-lamassu-admin/src/pages/Triggers/Wizard.js
+++ b/new-lamassu-admin/src/pages/Triggers/Wizard.js
@@ -48,14 +48,14 @@ const styles = {
const useStyles = makeStyles(styles)
-const getStep = (step, currency) => {
+const getStep = (step, currency, customInfoRequests) => {
switch (step) {
// case 1:
// return txDirection
case 1:
return type(currency)
case 2:
- return requirements
+ return requirements(customInfoRequests)
default:
return Fragment
}
@@ -202,7 +202,7 @@ const GetValues = ({ setValues }) => {
return null
}
-const Wizard = ({ onClose, save, error, currency }) => {
+const Wizard = ({ onClose, save, error, currency, customInfoRequests }) => {
const classes = useStyles()
const [liveValues, setLiveValues] = useState({})
@@ -211,7 +211,7 @@ const Wizard = ({ onClose, save, error, currency }) => {
})
const isLastStep = step === LAST_STEP
- const stepOptions = getStep(step, currency)
+ const stepOptions = getStep(step, currency, customInfoRequests)
const onContinue = async it => {
const newConfig = R.merge(config, stepOptions.schema.cast(it))
diff --git a/new-lamassu-admin/src/pages/Triggers/helper.js b/new-lamassu-admin/src/pages/Triggers/helper.js
index fcf91e71..f62b5ddf 100644
--- a/new-lamassu-admin/src/pages/Triggers/helper.js
+++ b/new-lamassu-admin/src/pages/Triggers/helper.js
@@ -1,8 +1,6 @@
-import { useQuery } from '@apollo/react-hooks'
import { makeStyles, Box } from '@material-ui/core'
import classnames from 'classnames'
import { Field, useFormikContext } from 'formik'
-import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { memo } from 'react'
import * as Yup from 'yup'
@@ -508,16 +506,7 @@ const requirementOptions = [
{ display: 'Block', code: 'block' }
]
-const GET_ACTIVE_CUSTOM_REQUESTS = gql`
- query customInfoRequests($onlyEnabled: Boolean) {
- customInfoRequests(onlyEnabled: $onlyEnabled) {
- id
- customRequest
- }
- }
-`
-
-const Requirement = () => {
+const Requirement = ({ customInfoRequests }) => {
const classes = useStyles()
const {
touched,
@@ -526,11 +515,6 @@ const Requirement = () => {
handleChange,
setTouched
} = useFormikContext()
- const { data } = useQuery(GET_ACTIVE_CUSTOM_REQUESTS, {
- variables: {
- onlyEnabled: true
- }
- })
const isSuspend = values?.requirement?.requirement === 'suspend'
const isCustom = values?.requirement?.requirement === 'custom'
@@ -546,8 +530,7 @@ const Requirement = () => {
(!values.requirement?.suspensionDays ||
values.requirement?.suspensionDays < 0)
- const customInfoRequests = R.path(['customInfoRequests'])(data) ?? []
- const enableCustomRequirement = customInfoRequests.length > 0
+ const enableCustomRequirement = customInfoRequests?.length > 0
const customInfoOption = {
display: 'Custom information requirement',
code: 'custom'
@@ -604,10 +587,11 @@ const Requirement = () => {
)
}
-const requirements = {
+const requirements = customInfoRequests => ({
schema: requirementSchema,
options: requirementOptions,
Component: Requirement,
+ props: { customInfoRequests },
initialValues: {
requirement: {
requirement: '',
@@ -615,7 +599,7 @@ const requirements = {
customInfoRequestId: ''
}
}
-}
+})
const getView = (data, code, compare) => it => {
if (!data) return ''
From dc1f8f82f9d7142ed06b2be185e84862798ad544 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Fri, 14 Jan 2022 19:45:18 +0000
Subject: [PATCH 35/51] fix: remove duplicate code
---
new-lamassu-admin/src/pages/Triggers/TriggerView.js | 5 +----
new-lamassu-admin/src/pages/Triggers/Triggers.js | 10 +++++++---
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Triggers/TriggerView.js b/new-lamassu-admin/src/pages/Triggers/TriggerView.js
index 93bbdc87..d07bdce9 100644
--- a/new-lamassu-admin/src/pages/Triggers/TriggerView.js
+++ b/new-lamassu-admin/src/pages/Triggers/TriggerView.js
@@ -75,10 +75,7 @@ const TriggerView = ({
error={error?.message}
save={add}
onClose={toggleWizard}
- customInfoRequests={R.filter(
- it => it.enabled === true,
- customInfoRequests
- )}
+ customInfoRequests={customInfoRequests}
/>
)}
{R.isEmpty(triggers) && (
diff --git a/new-lamassu-admin/src/pages/Triggers/Triggers.js b/new-lamassu-admin/src/pages/Triggers/Triggers.js
index af4046bc..c7e82550 100644
--- a/new-lamassu-admin/src/pages/Triggers/Triggers.js
+++ b/new-lamassu-admin/src/pages/Triggers/Triggers.js
@@ -48,8 +48,10 @@ const GET_CUSTOM_REQUESTS = gql`
const Triggers = () => {
const classes = useStyles()
const [wizardType, setWizard] = useState(false)
- const { data, loading } = useQuery(GET_CONFIG)
- const { data: customInfoReqData } = useQuery(GET_CUSTOM_REQUESTS)
+ const { data, loading: configLoading } = useQuery(GET_CONFIG)
+ const { data: customInfoReqData, loading: customInfoLoading } = useQuery(
+ GET_CUSTOM_REQUESTS
+ )
const [error, setError] = useState(null)
const [subMenu, setSubMenu] = useState(false)
@@ -94,6 +96,8 @@ const Triggers = () => {
return setWizard(wizardName)
}
+ const loading = configLoading || customInfoLoading
+
return (
<>
{
showWizard={wizardType === 'newTrigger'}
config={data?.config ?? {}}
toggleWizard={toggleWizard('newTrigger')}
- customInfoRequests={customInfoRequests}
+ customInfoRequests={enabledCustomInfoRequests}
/>
)}
{!loading && subMenu === 'advancedSettings' && (
From ebfdbbb3fc8ca098ca5e5eb09761d2b2fe613434 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Fri, 14 Jan 2022 21:05:55 +0000
Subject: [PATCH 36/51] fix: identify customers with manual pending data
---
.../src/pages/Customers/Customers.js | 12 +++++
.../src/pages/Customers/CustomersList.js | 4 +-
.../src/pages/Customers/helper.js | 46 ++++++++++++++++---
3 files changed, 54 insertions(+), 8 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js
index 70e182f6..ea584f9c 100644
--- a/new-lamassu-admin/src/pages/Customers/Customers.js
+++ b/new-lamassu-admin/src/pages/Customers/Customers.js
@@ -43,6 +43,16 @@ const GET_CUSTOMERS = gql`
lastTxFiatCode
lastTxClass
authorizedOverride
+ frontCameraPath
+ frontCameraOverride
+ idCardPhotoPath
+ idCardPhotoOverride
+ idCardData
+ idCardDataOverride
+ usSsn
+ usSsnOverride
+ sanctions
+ sanctionsOverride
daysSuspended
isSuspended
}
@@ -80,6 +90,7 @@ const Customers = () => {
const configData = R.path(['config'])(customersResponse) ?? []
const locale = configData && fromNamespace(namespaces.LOCALE, configData)
+ const triggers = configData && fromNamespace(namespaces.TRIGGERS, configData)
const customersData = R.sortWith([
R.descend(it => new Date(R.prop('lastActive', it) ?? '0'))
])(filteredCustomers ?? [])
@@ -168,6 +179,7 @@ const Customers = () => {
locale={locale}
onClick={handleCustomerClicked}
loading={customerLoading}
+ triggers={triggers}
/>
>
)
diff --git a/new-lamassu-admin/src/pages/Customers/CustomersList.js b/new-lamassu-admin/src/pages/Customers/CustomersList.js
index 43e40c2a..2684e36e 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomersList.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomersList.js
@@ -13,7 +13,7 @@ import { getAuthorizedStatus, getFormattedPhone, getName } from './helper'
const useStyles = makeStyles(styles)
-const CustomersList = ({ data, locale, onClick, loading }) => {
+const CustomersList = ({ data, locale, onClick, loading, triggers }) => {
const classes = useStyles()
const elements = [
@@ -66,7 +66,7 @@ const CustomersList = ({ data, locale, onClick, loading }) => {
{
header: 'Status',
width: 191,
- view: it =>
+ view: it =>
}
]
diff --git a/new-lamassu-admin/src/pages/Customers/helper.js b/new-lamassu-admin/src/pages/Customers/helper.js
index 719ae628..c21f1d7e 100644
--- a/new-lamassu-admin/src/pages/Customers/helper.js
+++ b/new-lamassu-admin/src/pages/Customers/helper.js
@@ -8,6 +8,7 @@ import * as Yup from 'yup'
import { RadioGroup, TextInput } from 'src/components/inputs/formik'
import { H4 } from 'src/components/typography'
import { errorColor } from 'src/styling/variables'
+import { MANUAL } from 'src/utils/constants'
import { Upload } from './components'
@@ -39,14 +40,47 @@ const useStyles = makeStyles({
const CUSTOMER_BLOCKED = 'blocked'
-const getAuthorizedStatus = it =>
- it.authorizedOverride === CUSTOMER_BLOCKED
- ? { label: 'Blocked', type: 'error' }
- : it.isSuspended
- ? it.daysSuspended > 0
+const getAuthorizedStatus = (it, triggers) => {
+ const fields = [
+ 'frontCameraPath',
+ 'idCardData',
+ 'idCardPhotoPath',
+ 'usSsn',
+ 'sanctions'
+ ]
+
+ const isManualField = fieldName => {
+ const manualOverrides = R.filter(
+ ite => R.equals(R.toLower(ite.automation), MANUAL),
+ triggers?.overrides ?? []
+ )
+
+ return (
+ !!R.find(ite => R.equals(ite.requirement, fieldName), manualOverrides) ||
+ R.equals(triggers.automation, MANUAL)
+ )
+ }
+
+ const pendingFieldStatus = R.map(
+ ite =>
+ !R.isNil(it[`${ite}`])
+ ? isManualField(ite)
+ ? R.equals(it[`${ite}Override`], 'automatic')
+ : false
+ : false,
+ fields
+ )
+
+ if (it.authorizedOverride === CUSTOMER_BLOCKED)
+ return { label: 'Blocked', type: 'error' }
+ if (it.isSuspended)
+ return it.daysSuspended > 0
? { label: `${it.daysSuspended} day suspension`, type: 'warning' }
: { label: `< 1 day suspension`, type: 'warning' }
- : { label: 'Authorized', type: 'success' }
+ if (R.any(ite => ite === true, pendingFieldStatus))
+ return { label: 'Pending', type: 'warning' }
+ return { label: 'Authorized', type: 'success' }
+}
const getFormattedPhone = (phone, country) => {
const phoneNumber =
From 8ad127c6c4819987f283e19cecec9e77410e8328 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Oliveira?=
Date: Mon, 17 Jan 2022 00:03:18 +0000
Subject: [PATCH 37/51] feat: enable customer data manual entry
---
.../src/pages/Customers/CustomerData.js | 48 ++--
.../src/pages/Customers/CustomerProfile.js | 35 ++-
.../src/pages/Customers/Wizard.js | 60 +++--
.../src/pages/Customers/components/Upload.js | 15 +-
.../src/pages/Customers/helper.js | 212 ++++++++++++++----
5 files changed, 274 insertions(+), 96 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js
index a5346a0e..3f792db3 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerData.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js
@@ -26,7 +26,11 @@ import { URI } from 'src/utils/apollo'
import styles from './CustomerData.styles.js'
import { EditableCard } from './components'
-import { customerDataElements, customerDataschemas } from './helper.js'
+import {
+ customerDataElements,
+ customerDataSchemas,
+ formatDates
+} from './helper.js'
const useStyles = makeStyles(styles)
@@ -64,7 +68,8 @@ const CustomerData = ({
editCustomer,
deleteEditedData,
updateCustomRequest,
- authorizeCustomRequest
+ authorizeCustomRequest,
+ updateCustomEntry
}) => {
const classes = useStyles()
const [listView, setListView] = useState(false)
@@ -96,7 +101,7 @@ const CustomerData = ({
const getVisibleCards = _.filter(elem => elem.isAvailable)
const initialValues = {
- idScan: {
+ idCardData: {
firstName: R.path(['firstName'])(idData) ?? '',
lastName: R.path(['lastName'])(idData) ?? '',
documentNumber: R.path(['documentNumber'])(idData) ?? '',
@@ -124,19 +129,9 @@ const CustomerData = ({
}
}
- const formatDates = values => {
- _.map(
- elem =>
- (values[elem] = format('yyyyMMdd')(
- parse(new Date(), 'yyyy-MM-dd', values[elem])
- ))
- )(['dateOfBirth', 'expirationDate'])
- return values
- }
-
const cards = [
{
- fields: customerDataElements.idScanElements,
+ fields: customerDataElements.idCardData,
title: 'ID Scan',
titleIcon: ,
state: R.path(['idCardDataOverride'])(customer),
@@ -148,8 +143,8 @@ const CustomerData = ({
editCustomer({
idCardData: _.merge(idData, formatDates(values))
}),
- validationSchema: customerDataschemas.idScan,
- initialValues: initialValues.idScan,
+ validationSchema: customerDataSchemas.idCardData,
+ initialValues: initialValues.idCardData,
isAvailable: !_.isNil(idData)
},
{
@@ -179,7 +174,7 @@ const CustomerData = ({
isAvailable: !_.isNil(sanctions)
},
{
- fields: customerDataElements.frontCameraElements,
+ fields: customerDataElements.frontCamera,
title: 'Front facing camera',
titleIcon: ,
state: R.path(['frontCameraOverride'])(customer),
@@ -201,12 +196,12 @@ const CustomerData = ({
/>
) : null,
hasImage: true,
- validationSchema: customerDataschemas.frontCamera,
+ validationSchema: customerDataSchemas.frontCamera,
initialValues: initialValues.frontCamera,
isAvailable: !_.isNil(customer.frontCameraPath)
},
{
- fields: customerDataElements.idCardPhotoElements,
+ fields: customerDataElements.idCardPhoto,
title: 'ID card image',
titleIcon: ,
state: R.path(['idCardPhotoOverride'])(customer),
@@ -226,20 +221,20 @@ const CustomerData = ({
/>
) : null,
hasImage: true,
- validationSchema: customerDataschemas.idCardPhoto,
+ validationSchema: customerDataSchemas.idCardPhoto,
initialValues: initialValues.idCardPhoto,
isAvailable: !_.isNil(customer.idCardPhotoPath)
},
{
- fields: customerDataElements.usSsnElements,
+ fields: customerDataElements.usSsn,
title: 'US SSN',
titleIcon: ,
state: R.path(['usSsnOverride'])(customer),
authorize: () => updateCustomer({ usSsnOverride: OVERRIDE_AUTHORIZED }),
reject: () => updateCustomer({ usSsnOverride: OVERRIDE_REJECTED }),
- save: values => editCustomer({ usSsn: values.usSsn }),
+ save: values => editCustomer(values),
deleteEditedData: () => deleteEditedData({ usSsn: null }),
- validationSchema: customerDataschemas.usSsn,
+ validationSchema: customerDataSchemas.usSsn,
initialValues: initialValues.usSsn,
isAvailable: !_.isNil(customer.usSsn)
}
@@ -308,7 +303,12 @@ const CustomerData = ({
],
title: it.label,
titleIcon: ,
- save: () => {},
+ save: values => {
+ updateCustomEntry({
+ fieldId: it.id,
+ value: values[it.label]
+ })
+ },
deleteEditedData: () => {},
validationSchema: Yup.object().shape({
[it.label]: Yup.string()
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
index d83960d3..71b24ba2 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
@@ -242,6 +242,12 @@ const SET_CUSTOM_ENTRY = gql`
}
`
+const EDIT_CUSTOM_ENTRY = gql`
+ mutation saveCustomField($customerId: ID!, $fieldId: ID!, $value: String!) {
+ saveCustomField(customerId: $customerId, fieldId: $fieldId, value: $value)
+ }
+`
+
const GET_ACTIVE_CUSTOM_REQUESTS = gql`
query customInfoRequests($onlyEnabled: Boolean) {
customInfoRequests(onlyEnabled: $onlyEnabled) {
@@ -280,6 +286,10 @@ const CustomerProfile = memo(() => {
onCompleted: () => getCustomer()
})
+ const [editCustomEntry] = useMutation(EDIT_CUSTOM_ENTRY, {
+ onCompleted: () => getCustomer()
+ })
+
const [replaceCustomerPhoto] = useMutation(REPLACE_CUSTOMER_PHOTO, {
onCompleted: () => getCustomer()
})
@@ -330,6 +340,16 @@ const CustomerProfile = memo(() => {
setWizard(null)
}
+ const updateCustomEntry = it => {
+ editCustomEntry({
+ variables: {
+ customerId,
+ fieldId: it.fieldId,
+ value: it.value
+ }
+ })
+ }
+
const updateCustomer = it =>
setCustomer({
variables: {
@@ -338,7 +358,7 @@ const CustomerProfile = memo(() => {
}
})
- const replacePhoto = it =>
+ const replacePhoto = it => {
replaceCustomerPhoto({
variables: {
customerId,
@@ -346,14 +366,18 @@ const CustomerProfile = memo(() => {
photoType: it.photoType
}
})
+ setWizard(null)
+ }
- const editCustomer = it =>
+ const editCustomer = it => {
editCustomerData({
variables: {
customerId,
customerEdit: it
}
})
+ setWizard(null)
+ }
const deleteEditedData = it =>
deleteCustomerEditedData({
@@ -421,7 +445,7 @@ const CustomerProfile = memo(() => {
const timezone = R.path(['config', 'locale_timezone'], configResponse)
- const customRequirementOptions =
+ const customInfoRequirementOptions =
activeCustomRequests?.customInfoRequests?.map(it => ({
value: it.id,
display: it.customRequest.name
@@ -564,7 +588,8 @@ const CustomerProfile = memo(() => {
editCustomer={editCustomer}
deleteEditedData={deleteEditedData}
updateCustomRequest={setCustomerCustomInfoRequest}
- authorizeCustomRequest={authorizeCustomRequest}>
+ authorizeCustomRequest={authorizeCustomRequest}
+ updateCustomEntry={updateCustomEntry}>
)}
{isNotes && (
@@ -590,7 +615,7 @@ const CustomerProfile = memo(() => {
addPhoto={replacePhoto}
addCustomerData={editCustomer}
onClose={() => setWizard(null)}
- customRequirementOptions={customRequirementOptions}
+ customInfoRequirementOptions={customInfoRequirementOptions}
/>
)}
diff --git a/new-lamassu-admin/src/pages/Customers/Wizard.js b/new-lamassu-admin/src/pages/Customers/Wizard.js
index 16db1647..f257e23f 100644
--- a/new-lamassu-admin/src/pages/Customers/Wizard.js
+++ b/new-lamassu-admin/src/pages/Customers/Wizard.js
@@ -1,5 +1,5 @@
import { makeStyles } from '@material-ui/core'
-import { Form, Formik, Field } from 'formik'
+import { Form, Formik } from 'formik'
import * as R from 'ramda'
import React, { useState, Fragment } from 'react'
@@ -7,10 +7,16 @@ import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal'
import Stepper from 'src/components/Stepper'
import { Button } from 'src/components/buttons'
-import { Dropdown } from 'src/components/inputs/formik'
import { comet } from 'src/styling/variables'
-import { entryType, customElements } from './helper'
+import {
+ entryType,
+ customElements,
+ requirementElements,
+ formatDates,
+ REQUIREMENT,
+ ID_CARD_DATA
+} from './helper'
const LAST_STEP = 2
@@ -52,11 +58,17 @@ const styles = {
const useStyles = makeStyles(styles)
const getStep = (step, selectedValues) => {
+ const elements =
+ selectedValues?.entryType === REQUIREMENT &&
+ !R.isNil(selectedValues?.requirement)
+ ? requirementElements[selectedValues?.requirement]
+ : customElements[selectedValues?.dataType]
+
switch (step) {
case 1:
return entryType
case 2:
- return customElements[selectedValues?.dataType]
+ return elements
default:
return Fragment
}
@@ -66,7 +78,7 @@ const Wizard = ({
onClose,
save,
error,
- customRequirementOptions,
+ customInfoRequirementOptions,
addCustomerData,
addPhoto
}) => {
@@ -78,7 +90,10 @@ const Wizard = ({
step: 1
})
- const isCustom = values => values?.requirement === 'custom'
+ const isIdCardData = values => values?.requirement === ID_CARD_DATA
+ const formatCustomerData = (it, newConfig) =>
+ isIdCardData(newConfig) ? { [newConfig.requirement]: formatDates(it) } : it
+
const isLastStep = step === LAST_STEP
const stepOptions = getStep(step, selectedValues)
@@ -87,7 +102,23 @@ const Wizard = ({
setSelectedValues(newConfig)
if (isLastStep) {
- return save(newConfig)
+ switch (stepOptions.saveType) {
+ case 'customerData':
+ return addCustomerData(formatCustomerData(it, newConfig))
+ case 'customerDataUpload':
+ return addPhoto({
+ newPhoto: R.head(R.values(it)),
+ photoType: R.head(R.keys(it))
+ })
+ case 'customEntry':
+ return save(newConfig)
+ case 'customInfoRequirement':
+ return
+ // case 'customerEntryUpload':
+ // break
+ default:
+ break
+ }
}
setState({
@@ -120,22 +151,9 @@ const Wizard = ({
)}
- {!R.isEmpty(data) && type === IMAGE && (
+ {!R.isEmpty(data) && isImage && (
)}
- {!R.isEmpty(data) && type !== IMAGE && (
+ {!R.isEmpty(data) && !isImage && (
{data.preview}
diff --git a/new-lamassu-admin/src/pages/Customers/helper.js b/new-lamassu-admin/src/pages/Customers/helper.js
index e6834459..009b21a5 100644
--- a/new-lamassu-admin/src/pages/Customers/helper.js
+++ b/new-lamassu-admin/src/pages/Customers/helper.js
@@ -1,12 +1,16 @@
import { makeStyles, Box } from '@material-ui/core'
import classnames from 'classnames'
-import { parse, isValid } from 'date-fns/fp'
+import { parse, isValid, format } from 'date-fns/fp'
import { Field, useFormikContext } from 'formik'
import { parsePhoneNumberFromString } from 'libphonenumber-js'
import * as R from 'ramda'
import * as Yup from 'yup'
-import { RadioGroup, TextInput } from 'src/components/inputs/formik'
+import {
+ RadioGroup,
+ TextInput,
+ Autocomplete
+} from 'src/components/inputs/formik'
import { H4 } from 'src/components/typography'
import { errorColor } from 'src/styling/variables'
@@ -35,10 +39,16 @@ const useStyles = makeStyles({
specialGrid: {
display: 'grid',
gridTemplateColumns: [[182, 162, 141]]
+ },
+ picker: {
+ width: 150
}
})
const CUSTOMER_BLOCKED = 'blocked'
+const CUSTOM = 'custom'
+const REQUIREMENT = 'requirement'
+const ID_CARD_DATA = 'idCardData'
const getAuthorizedStatus = it =>
it.authorizedOverride === CUSTOMER_BLOCKED
@@ -82,18 +92,28 @@ const requirementOptions = [
{ display: 'ID card image', code: 'idCardPhoto' },
{ display: 'ID data', code: 'idCardData' },
{ display: 'US SSN', code: 'usSsn' },
- { display: 'Customer camera', code: 'facephoto' }
+ { display: 'Customer camera', code: 'frontCamera' }
]
const customTextOptions = [
- { display: 'Data entry title', code: 'title' },
- { display: 'Data entry', code: 'data' }
+ { label: 'Data entry title', name: 'title' },
+ { label: 'Data entry', name: 'data' }
]
-const customUploadOptions = [{ display: 'Data entry title', code: 'title' }]
+const customUploadOptions = [{ label: 'Data entry title', name: 'title' }]
-const entryTypeSchema = Yup.object().shape({
- entryType: Yup.string().required()
+const entryTypeSchema = Yup.lazy(values => {
+ if (values.entryType === 'custom') {
+ return Yup.object().shape({
+ entryType: Yup.string().required(),
+ dataType: Yup.string().required()
+ })
+ } else if (values.entryType === 'requirement') {
+ return Yup.object().shape({
+ entryType: Yup.string().required(),
+ requirement: Yup.string().required()
+ })
+ }
})
const customFileSchema = Yup.object().shape({
@@ -111,13 +131,18 @@ const customTextSchema = Yup.object().shape({
data: Yup.string().required()
})
-const EntryType = ({ hasCustomRequirementOptions }) => {
+const updateRequirementOptions = it => [
+ {
+ display: 'Custom information requirement',
+ code: 'custom'
+ },
+ ...it
+]
+
+const EntryType = ({ customInfoRequirementOptions }) => {
const classes = useStyles()
const { values } = useFormikContext()
- const CUSTOM = 'custom'
- const REQUIREMENT = 'requirement'
-
const displayCustomOptions = values.entryType === CUSTOM
const displayRequirementOptions = values.entryType === REQUIREMENT
@@ -158,14 +183,8 @@ const EntryType = ({ hasCustomRequirementOptions }) => {
component={RadioGroup}
name="requirement"
options={
- hasCustomRequirementOptions
- ? [
- {
- display: 'Custom information requirement',
- code: 'custom'
- },
- ...requirementOptions
- ]
+ !R.isEmpty(customInfoRequirementOptions)
+ ? updateRequirementOptions(requirementOptions)
: requirementOptions
}
labelClassName={classes.label}
@@ -178,18 +197,68 @@ const EntryType = ({ hasCustomRequirementOptions }) => {
)
}
-const CustomData = ({ selectedValues }) => {
+const ManualDataEntry = ({ selectedValues, customInfoRequirementOptions }) => {
+ const classes = useStyles()
+
+ const typeOfEntrySelected = selectedValues?.entryType
const dataTypeSelected = selectedValues?.dataType
- const upload = dataTypeSelected === 'file' || dataTypeSelected === 'image'
+ const requirementSelected = selectedValues?.requirement
+
+ const displayRequirements = typeOfEntrySelected === 'requirement'
+
+ const isCustomInfoRequirement = requirementSelected === CUSTOM
+
+ const updatedRequirementOptions = !R.isEmpty(customInfoRequirementOptions)
+ ? updateRequirementOptions(requirementOptions)
+ : requirementOptions
+
+ const requirementName = displayRequirements
+ ? R.find(R.propEq('code', requirementSelected))(updatedRequirementOptions)
+ .display
+ : ''
+
+ const title = displayRequirements
+ ? `Requirement ${requirementName}`
+ : `Custom ${dataTypeSelected} entry`
+
+ const elements = displayRequirements
+ ? requirementElements[requirementSelected]
+ : customElements[dataTypeSelected]
+
+ const upload = displayRequirements
+ ? requirementSelected === 'idCardPhoto' ||
+ requirementSelected === 'frontCamera'
+ : dataTypeSelected === 'file' || dataTypeSelected === 'image'
+
return (
<>
- {`Custom ${dataTypeSelected} entry`}
+ {title}
- {customElements[dataTypeSelected].options.map(({ display, code }) => (
-
- ))}
- {upload && }
+ {isCustomInfoRequirement && (
+ {
+ // dispatch({ type: 'form', form: it })
+ }}
+ />
+ )}
+ {!upload &&
+ !isCustomInfoRequirement &&
+ elements.options.map(({ label, name }) => (
+
+ ))}
+ {upload && (
+
+ )}
>
)
}
@@ -198,20 +267,23 @@ const customElements = {
text: {
schema: customTextSchema,
options: customTextOptions,
- Component: CustomData,
- initialValues: { data: '', title: '' }
+ Component: ManualDataEntry,
+ initialValues: { data: '', title: '' },
+ saveType: 'customEntry'
},
file: {
schema: customFileSchema,
options: customUploadOptions,
- Component: CustomData,
- initialValues: { file: '', title: '' }
+ Component: ManualDataEntry,
+ initialValues: { file: null, title: '' },
+ saveType: 'customEntryUpload'
},
image: {
schema: customImageSchema,
options: customUploadOptions,
- Component: CustomData,
- initialValues: { image: '', title: '' }
+ Component: ManualDataEntry,
+ initialValues: { image: null, title: '' },
+ saveType: 'customEntryUpload'
}
}
@@ -225,7 +297,7 @@ const entryType = {
// Customer data
const customerDataElements = {
- idScanElements: [
+ idCardData: [
{
name: 'firstName',
label: 'First name',
@@ -262,7 +334,7 @@ const customerDataElements = {
component: TextInput
}
],
- usSsnElements: [
+ usSsn: [
{
name: 'usSsn',
label: 'US SSN',
@@ -270,12 +342,12 @@ const customerDataElements = {
size: 190
}
],
- idCardPhotoElements: [{ name: 'idCardPhoto' }],
- frontCameraElements: [{ name: 'frontCamera' }]
+ idCardPhoto: [{ name: 'idCardPhoto' }],
+ frontCamera: [{ name: 'frontCamera' }]
}
-const customerDataschemas = {
- idScan: Yup.object().shape({
+const customerDataSchemas = {
+ idCardData: Yup.object().shape({
firstName: Yup.string().required(),
lastName: Yup.string().required(),
documentNumber: Yup.string().required(),
@@ -303,6 +375,61 @@ const customerDataschemas = {
})
}
+const requirementElements = {
+ idCardData: {
+ schema: customerDataSchemas.idCardData,
+ options: customerDataElements.idCardData,
+ Component: ManualDataEntry,
+ initialValues: {
+ firstName: '',
+ lastName: '',
+ documentNumber: '',
+ dateOfBirth: '',
+ gender: '',
+ country: '',
+ expirationDate: ''
+ },
+ saveType: 'customerData'
+ },
+ usSsn: {
+ schema: customerDataSchemas.usSsn,
+ options: customerDataElements.usSsn,
+ Component: ManualDataEntry,
+ initialValues: { usSsn: '' },
+ saveType: 'customerData'
+ },
+ idCardPhoto: {
+ schema: customerDataSchemas.idCardPhoto,
+ options: customerDataElements.idCardPhoto,
+ Component: ManualDataEntry,
+ initialValues: { idCardPhoto: null },
+ saveType: 'customerDataUpload'
+ },
+ frontCamera: {
+ schema: customerDataSchemas.frontCamera,
+ options: customerDataElements.frontCamera,
+ Component: ManualDataEntry,
+ initialValues: { frontCamera: null },
+ saveType: 'customerDataUpload'
+ },
+ custom: {
+ // schema: customerDataSchemas.customInfoRequirement,
+ Component: ManualDataEntry,
+ initialValues: { customInfoRequirement: null },
+ saveType: 'customInfoRequirement'
+ }
+}
+
+const formatDates = values => {
+ R.map(
+ elem =>
+ (values[elem] = format('yyyyMMdd')(
+ parse(new Date(), 'yyyy-MM-dd', values[elem])
+ ))
+ )(['dateOfBirth', 'expirationDate'])
+ return values
+}
+
const mapKeys = pair => {
const [key, value] = pair
if (key === 'txCustomerPhotoPath' || key === 'frontCameraPath') {
@@ -339,7 +466,12 @@ export {
getName,
entryType,
customElements,
+ requirementElements,
formatPhotosData,
customerDataElements,
- customerDataschemas
+ customerDataSchemas,
+ formatDates,
+ REQUIREMENT,
+ CUSTOM,
+ ID_CARD_DATA
}
From f4f4eb13357f4a283a90eb4b59dc24499c982292 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Oliveira?=
Date: Mon, 17 Jan 2022 16:57:37 +0000
Subject: [PATCH 38/51] fix: disable unfinished features
---
.../graphql/resolvers/customer.resolver.js | 2 +-
new-lamassu-admin/src/components/Carousel.js | 6 ++++
.../src/pages/Customers/CustomerData.js | 31 +++++++++++--------
.../src/pages/Customers/CustomerProfile.js | 11 ++++---
.../Customers/components/EditableCard.js | 24 +++++++-------
.../src/pages/Customers/helper.js | 30 +++++++++++++-----
6 files changed, 67 insertions(+), 37 deletions(-)
diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js
index d3563669..bbe46195 100644
--- a/lib/new-admin/graphql/resolvers/customer.resolver.js
+++ b/lib/new-admin/graphql/resolvers/customer.resolver.js
@@ -21,7 +21,7 @@ const resolvers = {
return customers.updateCustomer(customerId, customerInput, token)
},
addCustomField: (...[, { customerId, label, value }]) => customers.addCustomField(customerId, label, value),
- saveCustomField: (...[, { customerId, fieldId, newValue }]) => customers.saveCustomField(customerId, fieldId, newValue),
+ saveCustomField: (...[, { customerId, fieldId, value }]) => customers.saveCustomField(customerId, fieldId, value),
removeCustomField: (...[, [ { customerId, fieldId } ]]) => customers.removeCustomField(customerId, fieldId),
editCustomer: async (root, { customerId, customerEdit }, context) => {
const token = authentication.getToken(context)
diff --git a/new-lamassu-admin/src/components/Carousel.js b/new-lamassu-admin/src/components/Carousel.js
index f751cd1f..35f4ef20 100644
--- a/new-lamassu-admin/src/components/Carousel.js
+++ b/new-lamassu-admin/src/components/Carousel.js
@@ -38,6 +38,12 @@ export const Carousel = memo(({ photosData, slidePhoto }) => {
opacity: 1
}
}}
+ // navButtonsWrapperProps={{
+ // style: {
+ // background: 'linear-gradient(to right, black 10%, transparent 80%)',
+ // opacity: '0.4'
+ // }
+ // }}
autoPlay={false}
indicators={false}
navButtonsAlwaysVisible={true}
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js
index 3f792db3..1a06b233 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerData.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js
@@ -360,19 +360,24 @@ const CustomerData = ({
{'Customer data'}
- setListView(false)}
- />
- setListView(true)}>
+ {// TODO: Remove false condition for next release
+ false && (
+ <>
+ setListView(false)}
+ />
+ setListView(true)}>
+ >
+ )}
{!listView && customer && (
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
index 71b24ba2..8ea827a1 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
@@ -18,8 +18,8 @@ import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/bl
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/zodiac.svg'
import { ReactComponent as DataReversedIcon } from 'src/styling/icons/button/data/white.svg'
import { ReactComponent as DataIcon } from 'src/styling/icons/button/data/zodiac.svg'
-import { ReactComponent as DiscountReversedIcon } from 'src/styling/icons/button/discount/white.svg'
-import { ReactComponent as Discount } from 'src/styling/icons/button/discount/zodiac.svg'
+// import { ReactComponent as DiscountReversedIcon } from 'src/styling/icons/button/discount/white.svg'
+// import { ReactComponent as Discount } from 'src/styling/icons/button/discount/zodiac.svg'
import { fromNamespace, namespaces } from 'src/utils/config'
import CustomerData from './CustomerData'
@@ -494,14 +494,17 @@ const CustomerProfile = memo(() => {
onClick={() => setWizard(true)}>
{`Manual data entry`}
-
{}}>
{`Add individual discount`}
-
+ */
+ }
{isSuspended && (
{!editing && (
-
-
deleteEditedData()}>
- {`Delete`}
-
-
-
+ {// TODO: Remove false condition for next release
+ false && (
+
+
deleteEditedData()}>
+ {`Delete`}
+
+
+ )}
*:last-child': {
+ marginBottom: 24
+ }
}
})
@@ -183,9 +188,11 @@ const EntryType = ({ customInfoRequirementOptions }) => {
component={RadioGroup}
name="requirement"
options={
- !R.isEmpty(customInfoRequirementOptions)
- ? updateRequirementOptions(requirementOptions)
- : requirementOptions
+ requirementOptions
+ // TODO: Enable once custom info requirement manual entry is finished
+ // !R.isEmpty(customInfoRequirementOptions)
+ // ? updateRequirementOptions(requirementOptions)
+ // : requirementOptions
}
labelClassName={classes.label}
radioClassName={classes.radio}
@@ -248,11 +255,18 @@ const ManualDataEntry = ({ selectedValues, customInfoRequirementOptions }) => {
}}
/>
)}
- {!upload &&
- !isCustomInfoRequirement &&
- elements.options.map(({ label, name }) => (
-
- ))}
+
+ {!upload &&
+ !isCustomInfoRequirement &&
+ elements.options.map(({ label, name }) => (
+
+ ))}
+
{upload && (
Date: Mon, 17 Jan 2022 17:06:45 +0000
Subject: [PATCH 39/51] fix: mark TODOs
---
new-lamassu-admin/src/pages/Customers/CustomerProfile.js | 1 +
new-lamassu-admin/src/pages/Customers/helper.js | 4 +---
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
index 8ea827a1..59d0202c 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
@@ -18,6 +18,7 @@ import { ReactComponent as BlockReversedIcon } from 'src/styling/icons/button/bl
import { ReactComponent as BlockIcon } from 'src/styling/icons/button/block/zodiac.svg'
import { ReactComponent as DataReversedIcon } from 'src/styling/icons/button/data/white.svg'
import { ReactComponent as DataIcon } from 'src/styling/icons/button/data/zodiac.svg'
+// TODO: Enable for next release
// import { ReactComponent as DiscountReversedIcon } from 'src/styling/icons/button/discount/white.svg'
// import { ReactComponent as Discount } from 'src/styling/icons/button/discount/zodiac.svg'
import { fromNamespace, namespaces } from 'src/utils/config'
diff --git a/new-lamassu-admin/src/pages/Customers/helper.js b/new-lamassu-admin/src/pages/Customers/helper.js
index c0d86f55..b991e76d 100644
--- a/new-lamassu-admin/src/pages/Customers/helper.js
+++ b/new-lamassu-admin/src/pages/Customers/helper.js
@@ -250,9 +250,7 @@ const ManualDataEntry = ({ selectedValues, customInfoRequirementOptions }) => {
getOptionSelected={R.eqProps('code')}
labelProp={'display'}
options={customInfoRequirementOptions}
- onChange={(evt, it) => {
- // dispatch({ type: 'form', form: it })
- }}
+ onChange={(evt, it) => {}}
/>
)}
From dc4a8471d47c4b6f619c8415c0be034f2636d299 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Mon, 17 Jan 2022 17:48:00 +0000
Subject: [PATCH 40/51] fix: set notification card design up to spec
---
.../NotificationCenter.styles.js | 9 +++--
.../NotificationCenter/NotificationRow.js | 35 ++++++++++++++-----
.../styling/icons/action/wrench/zodiac.svg | 2 +-
3 files changed, 34 insertions(+), 12 deletions(-)
diff --git a/new-lamassu-admin/src/components/NotificationCenter/NotificationCenter.styles.js b/new-lamassu-admin/src/components/NotificationCenter/NotificationCenter.styles.js
index 49f0456f..91d1b720 100644
--- a/new-lamassu-admin/src/components/NotificationCenter/NotificationCenter.styles.js
+++ b/new-lamassu-admin/src/components/NotificationCenter/NotificationCenter.styles.js
@@ -64,6 +64,9 @@ const styles = {
position: 'relative',
marginBottom: spacer / 2,
paddingTop: spacer * 1.5,
+ '& > *:first-child': {
+ marginRight: 24
+ },
'& > *': {
marginRight: 10
},
@@ -74,7 +77,8 @@ const styles = {
notificationContent: {
display: 'flex',
flexDirection: 'column',
- justifyContent: 'center'
+ justifyContent: 'center',
+ width: 300
},
unread: {
backgroundColor: spring3
@@ -89,8 +93,7 @@ const styles = {
flexGrow: 1
},
unreadIcon: {
- marginLeft: spacer,
- marginTop: 5,
+ marginTop: 2,
width: '12px',
height: '12px',
backgroundColor: secondaryColor,
diff --git a/new-lamassu-admin/src/components/NotificationCenter/NotificationRow.js b/new-lamassu-admin/src/components/NotificationCenter/NotificationRow.js
index e318f0c2..f5cdea17 100644
--- a/new-lamassu-admin/src/components/NotificationCenter/NotificationRow.js
+++ b/new-lamassu-admin/src/components/NotificationCenter/NotificationRow.js
@@ -13,12 +13,27 @@ import styles from './NotificationCenter.styles'
const useStyles = makeStyles(styles)
const types = {
- transaction: { display: 'Transactions', icon: },
- highValueTransaction: { display: 'Transactions', icon: },
- fiatBalance: { display: 'Maintenance', icon: },
- cryptoBalance: { display: 'Maintenance', icon: },
- compliance: { display: 'Compliance', icon: },
- error: { display: 'Error', icon: }
+ transaction: {
+ display: 'Transactions',
+ icon:
+ },
+ highValueTransaction: {
+ display: 'Transactions',
+ icon:
+ },
+ fiatBalance: {
+ display: 'Maintenance',
+ icon:
+ },
+ cryptoBalance: {
+ display: 'Maintenance',
+ icon:
+ },
+ compliance: {
+ display: 'Compliance',
+ icon:
+ },
+ error: { display: 'Error', icon: }
}
const NotificationRow = ({
@@ -35,7 +50,9 @@ const NotificationRow = ({
const classes = useStyles()
const typeDisplay = R.path([type, 'display'])(types) ?? null
- const icon = R.path([type, 'icon'])(types) ??
+ const icon = R.path([type, 'icon'])(types) ?? (
+
+ )
const age = prettyMs(new Date().getTime() - new Date(created).getTime(), {
compact: true,
verbose: true
@@ -57,7 +74,9 @@ const NotificationRow = ({
classes.notificationRow,
!read && valid ? classes.unread : ''
)}>
- {icon}
+
{notificationTitle}
diff --git a/new-lamassu-admin/src/styling/icons/action/wrench/zodiac.svg b/new-lamassu-admin/src/styling/icons/action/wrench/zodiac.svg
index 58db5b0d..0cf3417a 100644
--- a/new-lamassu-admin/src/styling/icons/action/wrench/zodiac.svg
+++ b/new-lamassu-admin/src/styling/icons/action/wrench/zodiac.svg
@@ -1,5 +1,5 @@
-
+
From f550da4d1e84e2622b5d8e8b4f040527cf239906 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Fri, 14 Jan 2022 18:24:13 +0000
Subject: [PATCH 41/51] fix: add batched but not confirmed customer
transactions to last active timestamp
---
lib/customers.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/customers.js b/lib/customers.js
index e96155f9..954c550d 100644
--- a/lib/customers.js
+++ b/lib/customers.js
@@ -646,7 +646,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
coalesce(sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (partition by c.id), 0) AS total_spent, ccf.custom_fields
FROM customers c LEFT OUTER JOIN (
SELECT 'cashIn' AS tx_class, id, fiat, fiat_code, created, customer_id, error_code
- FROM cash_in_txs WHERE send_confirmed = true UNION
+ FROM cash_in_txs WHERE send_confirmed = true OR batched = true UNION
SELECT 'cashOut' AS tx_class, id, fiat, fiat_code, created, customer_id, error_code
FROM cash_out_txs WHERE confirmed_at IS NOT NULL) AS t ON c.id = t.customer_id
LEFT OUTER JOIN (
@@ -701,7 +701,7 @@ function getCustomerById (id) {
sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (PARTITION BY c.id) AS total_spent, ccf.custom_fields
FROM customers c LEFT OUTER JOIN (
SELECT 'cashIn' AS tx_class, id, fiat, fiat_code, created, customer_id, error_code
- FROM cash_in_txs WHERE send_confirmed = true UNION
+ FROM cash_in_txs WHERE send_confirmed = true OR batched = true UNION
SELECT 'cashOut' AS tx_class, id, fiat, fiat_code, created, customer_id, error_code
FROM cash_out_txs WHERE confirmed_at IS NOT NULL) t ON c.id = t.customer_id
LEFT OUTER JOIN (
From 5e9d00fa29ac9b35cff19f767219a31fb9d20702 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Mon, 17 Jan 2022 18:11:34 +0000
Subject: [PATCH 42/51] fix: make customer creation count as customer activity
---
lib/customers.js | 8 ++++----
new-lamassu-admin/src/pages/Customers/Customers.js | 7 ++++++-
2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/lib/customers.js b/lib/customers.js
index 954c550d..1c93d261 100644
--- a/lib/customers.js
+++ b/lib/customers.js
@@ -631,7 +631,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_override,
phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration,
id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at,
- sanctions_override, total_txs, total_spent, created AS last_active, fiat AS last_tx_fiat,
+ sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, custom_fields, notes
FROM (
SELECT c.id, c.authorized_override,
@@ -640,7 +640,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
c.front_camera_path, c.front_camera_override,
c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions,
- c.sanctions_at, c.sanctions_override, t.tx_class, t.fiat, t.fiat_code, t.created, cn.notes,
+ c.sanctions_at, c.sanctions_override, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
row_number() OVER (partition by c.id order by t.created desc) AS rn,
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (partition by c.id) AS total_txs,
coalesce(sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (partition by c.id), 0) AS total_spent, ccf.custom_fields
@@ -686,7 +686,7 @@ function getCustomerById (id) {
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_at, front_camera_override,
phone, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
id_card_photo_path, id_card_photo_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
- sanctions_override, total_txs, total_spent, created AS last_active, fiat AS last_tx_fiat,
+ sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes
FROM (
SELECT c.id, c.authorized_override,
@@ -695,7 +695,7 @@ function getCustomerById (id) {
c.front_camera_path, c.front_camera_override, c.front_camera_at,
c.phone, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
- c.sanctions_at, c.sanctions_override, c.subscriber_info, t.tx_class, t.fiat, t.fiat_code, t.created, cn.notes,
+ c.sanctions_at, c.sanctions_override, c.subscriber_info, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY c.id) AS total_txs,
sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (PARTITION BY c.id) AS total_spent, ccf.custom_fields
diff --git a/new-lamassu-admin/src/pages/Customers/Customers.js b/new-lamassu-admin/src/pages/Customers/Customers.js
index 1875d44d..e24e5586 100644
--- a/new-lamassu-admin/src/pages/Customers/Customers.js
+++ b/new-lamassu-admin/src/pages/Customers/Customers.js
@@ -91,7 +91,12 @@ const Customers = () => {
const [createNewCustomer] = useMutation(CREATE_CUSTOMER, {
onCompleted: () => setShowCreationModal(false),
- refetchQueries: () => ['configAndCustomers']
+ refetchQueries: () => [
+ {
+ query: GET_CUSTOMERS,
+ variables
+ }
+ ]
})
const configData = R.path(['config'])(customersResponse) ?? []
From 1db68114a2aef458cd7f01123c4b5f86aecb8177 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Mon, 17 Jan 2022 18:27:26 +0000
Subject: [PATCH 43/51] fix: remove undefined field querying
---
new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js b/new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js
index 6eef4ae4..c84d4b82 100644
--- a/new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js
+++ b/new-lamassu-admin/src/pages/Maintenance/CashboxHistory.js
@@ -28,7 +28,6 @@ const GET_BATCHES = gql`
fiat
deviceId
created
- cashbox
}
}
}
From 586c3f12cdcdad2c5ab96b4ebb4257ef7fd135d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Oliveira?=
Date: Mon, 17 Jan 2022 21:27:29 +0000
Subject: [PATCH 44/51] fix: remove formik values
---
.../src/pages/Customers/Wizard.js | 28 +++++++++----------
1 file changed, 13 insertions(+), 15 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Customers/Wizard.js b/new-lamassu-admin/src/pages/Customers/Wizard.js
index f257e23f..6f81b9de 100644
--- a/new-lamassu-admin/src/pages/Customers/Wizard.js
+++ b/new-lamassu-admin/src/pages/Customers/Wizard.js
@@ -147,21 +147,19 @@ const Wizard = ({
onSubmit={onContinue}
initialValues={stepOptions.initialValues}
validationSchema={stepOptions.schema}>
- {({ values }) => (
-
- )}
+
>
From 807c5bfc8592a0c9a238957a36d731640eb3b61c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Wed, 22 Dec 2021 18:31:32 +0000
Subject: [PATCH 45/51] fix: triggers wizard allowing empty custom requirement
field
fix: issue with custom requirement filtering when in presence of older triggers
refactor: pull up methods
fix: remove log
---
lib/routes/pollingRoutes.js | 4 +-
.../src/pages/Triggers/Wizard.js | 22 +++++--
.../src/pages/Triggers/helper.js | 66 ++++++++++++++-----
3 files changed, 67 insertions(+), 25 deletions(-)
diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js
index ad7d0118..b57db854 100644
--- a/lib/routes/pollingRoutes.js
+++ b/lib/routes/pollingRoutes.js
@@ -41,8 +41,8 @@ const createTerms = terms => (terms.active && terms.text) ? ({
const buildTriggers = (allTriggers) => {
const normalTriggers = []
const customTriggers = _.filter(o => {
- if (o.customInfoRequestId === '') normalTriggers.push(o)
- return o.customInfoRequestId !== ''
+ if (o.customInfoRequestId === '' || _.isNil(o.customInfoRequestId)) normalTriggers.push(o)
+ return !_.isNil(o.customInfoRequestId)
}, allTriggers)
return _.flow([_.map(_.get('customInfoRequestId')), customRequestQueries.batchGetCustomInfoRequest])(customTriggers)
diff --git a/new-lamassu-admin/src/pages/Triggers/Wizard.js b/new-lamassu-admin/src/pages/Triggers/Wizard.js
index dfc5fd03..050f272e 100644
--- a/new-lamassu-admin/src/pages/Triggers/Wizard.js
+++ b/new-lamassu-admin/src/pages/Triggers/Wizard.js
@@ -230,12 +230,18 @@ const Wizard = ({ onClose, save, error, currency, customInfoRequests }) => {
const triggerType = values?.triggerType
const containsType = R.contains(triggerType)
const isSuspend = values?.requirement?.requirement === 'suspend'
+ const isCustom = values?.requirement?.requirement === 'custom'
- const hasRequirementError =
- !!errors.requirement &&
- !!touched.requirement?.suspensionDays &&
- (!values.requirement?.suspensionDays ||
- values.requirement?.suspensionDays < 0)
+ const hasRequirementError = requirements.hasRequirementError(
+ errors,
+ touched,
+ values
+ )
+ const hasCustomRequirementError = requirements.hasCustomRequirementError(
+ errors,
+ touched,
+ values
+ )
const hasAmountError =
!!errors.threshold &&
@@ -258,7 +264,11 @@ const Wizard = ({ onClose, save, error, currency, customInfoRequests }) => {
)
return errors.threshold
- if (isSuspend && hasRequirementError) return errors.requirement
+ if (
+ (isSuspend && hasRequirementError) ||
+ (isCustom && hasCustomRequirementError)
+ )
+ return errors.requirement
}
return (
diff --git a/new-lamassu-admin/src/pages/Triggers/helper.js b/new-lamassu-admin/src/pages/Triggers/helper.js
index f62b5ddf..6889fc66 100644
--- a/new-lamassu-admin/src/pages/Triggers/helper.js
+++ b/new-lamassu-admin/src/pages/Triggers/helper.js
@@ -477,21 +477,43 @@ const requirementSchema = Yup.object()
otherwise: Yup.number()
.nullable()
.transform(() => null)
+ }),
+ customInfoRequestId: Yup.string().when('requirement', {
+ is: value => value === 'custom',
+ then: Yup.string(),
+ otherwise: Yup.string()
+ .nullable()
+ .transform(() => '')
})
}).required()
})
.test(({ requirement }, context) => {
- const requirementValidator = requirement =>
- requirement.requirement === 'suspend'
- ? requirement.suspensionDays > 0
- : true
+ const requirementValidator = (requirement, type) => {
+ switch (type) {
+ case 'suspend':
+ return requirement.requirement === type
+ ? requirement.suspensionDays > 0
+ : true
+ case 'custom':
+ return requirement.requirement === type
+ ? !R.isNil(requirement.customInfoRequestId)
+ : true
+ default:
+ return true
+ }
+ }
- if (requirement && requirementValidator(requirement)) return
+ if (requirement && !requirementValidator(requirement, 'suspend'))
+ return context.createError({
+ path: 'requirement',
+ message: 'Suspension days must be greater than 0'
+ })
- return context.createError({
- path: 'requirement',
- message: 'Suspension days must be greater than 0'
- })
+ if (requirement && !requirementValidator(requirement, 'custom'))
+ return context.createError({
+ path: 'requirement',
+ message: 'You must select an item'
+ })
})
const requirementOptions = [
@@ -506,6 +528,18 @@ const requirementOptions = [
{ display: 'Block', code: 'block' }
]
+const hasRequirementError = (errors, touched, values) =>
+ !!errors.requirement &&
+ !!touched.requirement?.suspensionDays &&
+ (!values.requirement?.suspensionDays ||
+ values.requirement?.suspensionDays < 0)
+
+const hasCustomRequirementError = (errors, touched, values) =>
+ !!errors.requirement &&
+ !!touched.requirement?.customInfoRequestId &&
+ (!values.requirement?.customInfoRequestId ||
+ !R.isNil(values.requirement?.customInfoRequestId))
+
const Requirement = ({ customInfoRequests }) => {
const classes = useStyles()
const {
@@ -524,12 +558,6 @@ const Requirement = ({ customInfoRequests }) => {
display: it.customRequest.name
}))
- const hasRequirementError =
- !!errors.requirement &&
- !!touched.requirement?.suspensionDays &&
- (!values.requirement?.suspensionDays ||
- values.requirement?.suspensionDays < 0)
-
const enableCustomRequirement = customInfoRequests?.length > 0
const customInfoOption = {
display: 'Custom information requirement',
@@ -540,7 +568,9 @@ const Requirement = ({ customInfoRequests }) => {
: [...requirementOptions, { ...customInfoOption, disabled: true }]
const titleClass = {
[classes.error]:
- (!!errors.requirement && !isSuspend) || (isSuspend && hasRequirementError)
+ (!!errors.requirement && !isSuspend && !isCustom) ||
+ (isSuspend && hasRequirementError(errors, touched, values)) ||
+ (isCustom && hasCustomRequirementError(errors, touched, values))
}
return (
@@ -569,7 +599,7 @@ const Requirement = ({ customInfoRequests }) => {
label="Days"
size="lg"
name="requirement.suspensionDays"
- error={hasRequirementError}
+ error={hasRequirementError(errors, touched, values)}
/>
)}
{isCustom && (
@@ -592,6 +622,8 @@ const requirements = customInfoRequests => ({
options: requirementOptions,
Component: Requirement,
props: { customInfoRequests },
+ hasRequirementError: hasRequirementError,
+ hasCustomRequirementError: hasCustomRequirementError,
initialValues: {
requirement: {
requirement: '',
From c241c458d32b853c2696317e4b00fe176074a7e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Mon, 17 Jan 2022 23:59:43 +0000
Subject: [PATCH 46/51] fix: custom info request batching fix: issue from
rebase
---
lib/routes/pollingRoutes.js | 4 ++--
new-lamassu-admin/src/pages/Triggers/Wizard.js | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js
index b57db854..b4089d54 100644
--- a/lib/routes/pollingRoutes.js
+++ b/lib/routes/pollingRoutes.js
@@ -41,8 +41,8 @@ const createTerms = terms => (terms.active && terms.text) ? ({
const buildTriggers = (allTriggers) => {
const normalTriggers = []
const customTriggers = _.filter(o => {
- if (o.customInfoRequestId === '' || _.isNil(o.customInfoRequestId)) normalTriggers.push(o)
- return !_.isNil(o.customInfoRequestId)
+ if (_.isEmpty(o.customInfoRequestId) || _.isNil(o.customInfoRequestId)) normalTriggers.push(o)
+ return !_.isNil(o.customInfoRequestId) && !_.isEmpty(o.customInfoRequestId)
}, allTriggers)
return _.flow([_.map(_.get('customInfoRequestId')), customRequestQueries.batchGetCustomInfoRequest])(customTriggers)
diff --git a/new-lamassu-admin/src/pages/Triggers/Wizard.js b/new-lamassu-admin/src/pages/Triggers/Wizard.js
index 050f272e..b78fc5fa 100644
--- a/new-lamassu-admin/src/pages/Triggers/Wizard.js
+++ b/new-lamassu-admin/src/pages/Triggers/Wizard.js
@@ -232,12 +232,12 @@ const Wizard = ({ onClose, save, error, currency, customInfoRequests }) => {
const isSuspend = values?.requirement?.requirement === 'suspend'
const isCustom = values?.requirement?.requirement === 'custom'
- const hasRequirementError = requirements.hasRequirementError(
+ const hasRequirementError = requirements().hasRequirementError(
errors,
touched,
values
)
- const hasCustomRequirementError = requirements.hasCustomRequirementError(
+ const hasCustomRequirementError = requirements().hasCustomRequirementError(
errors,
touched,
values
From 87a3b718db3a75c7a726f3e47f425fce0d302d24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Tue, 18 Jan 2022 18:46:55 +0000
Subject: [PATCH 47/51] fix: allow for custom info requests to be allowed or
blocked fix: improve custom info requests backend fix: add custom info
requests to trigger overrides
---
.../resolvers/customInfoRequests.resolver.js | 6 +-
.../graphql/types/customInfoRequests.type.js | 6 +-
lib/new-admin/services/customInfoRequests.js | 26 +++--
lib/new-config-manager.js | 34 +++---
lib/routes/pollingRoutes.js | 6 +-
...42518884925-manual-custom-info-requests.js | 16 +++
.../src/pages/Customers/CustomerData.js | 6 +-
.../src/pages/Customers/CustomerProfile.js | 8 +-
.../components/CustomInfoRequestsData.js | 4 +-
.../Triggers/components/AdvancedTriggers.js | 100 +++++++++++-------
.../src/pages/Triggers/components/helper.js | 43 +++++---
11 files changed, 167 insertions(+), 88 deletions(-)
create mode 100644 migrations/1642518884925-manual-custom-info-requests.js
diff --git a/lib/new-admin/graphql/resolvers/customInfoRequests.resolver.js b/lib/new-admin/graphql/resolvers/customInfoRequests.resolver.js
index 897b415c..fefdcf6b 100644
--- a/lib/new-admin/graphql/resolvers/customInfoRequests.resolver.js
+++ b/lib/new-admin/graphql/resolvers/customInfoRequests.resolver.js
@@ -1,3 +1,4 @@
+const authentication = require('../modules/userManagement')
const queries = require('../../services/customInfoRequests')
const DataLoader = require('dataloader')
@@ -21,7 +22,10 @@ const resolvers = {
insertCustomInfoRequest: (...[, { customRequest }]) => queries.addCustomInfoRequest(customRequest),
removeCustomInfoRequest: (...[, { id }]) => queries.removeCustomInfoRequest(id),
editCustomInfoRequest: (...[, { id, customRequest }]) => queries.editCustomInfoRequest(id, customRequest),
- setAuthorizedCustomRequest: (...[, { customerId, infoRequestId, isAuthorized }]) => queries.setAuthorizedCustomRequest(customerId, infoRequestId, isAuthorized),
+ setAuthorizedCustomRequest: (...[, { customerId, infoRequestId, override }, context]) => {
+ const token = authentication.getToken(context)
+ return queries.setAuthorizedCustomRequest(customerId, infoRequestId, override, token)
+ },
setCustomerCustomInfoRequest: (...[, { customerId, infoRequestId, data }]) => queries.setCustomerData(customerId, infoRequestId, data)
}
}
diff --git a/lib/new-admin/graphql/types/customInfoRequests.type.js b/lib/new-admin/graphql/types/customInfoRequests.type.js
index 5a9ed909..917c9f6f 100644
--- a/lib/new-admin/graphql/types/customInfoRequests.type.js
+++ b/lib/new-admin/graphql/types/customInfoRequests.type.js
@@ -32,7 +32,9 @@ const typeDef = gql`
type CustomRequestData {
customerId: ID
infoRequestId: ID
- approved: Boolean
+ override: String
+ overrideAt: Date
+ overrideBy: ID
customerData: JSON
customInfoRequest: CustomInfoRequest
}
@@ -47,7 +49,7 @@ const typeDef = gql`
insertCustomInfoRequest(customRequest: CustomRequestInput!): CustomInfoRequest @auth
removeCustomInfoRequest(id: ID!): CustomInfoRequest @auth
editCustomInfoRequest(id: ID!, customRequest: CustomRequestInput!): CustomInfoRequest @auth
- setAuthorizedCustomRequest(customerId: ID!, infoRequestId: ID!, isAuthorized: Boolean!): Boolean @auth
+ setAuthorizedCustomRequest(customerId: ID!, infoRequestId: ID!, override: String!): Boolean @auth
setCustomerCustomInfoRequest(customerId: ID!, infoRequestId: ID!, data: JSON!): Boolean @auth
}
`
diff --git a/lib/new-admin/services/customInfoRequests.js b/lib/new-admin/services/customInfoRequests.js
index c19c112a..7c703443 100644
--- a/lib/new-admin/services/customInfoRequests.js
+++ b/lib/new-admin/services/customInfoRequests.js
@@ -35,8 +35,10 @@ const getAllCustomInfoRequestsForCustomer = (customerId) => {
return db.any(sql, [customerId]).then(res => res.map(item => ({
customerId: item.customer_id,
infoRequestId: item.info_request_id,
- approved: item.approved,
- customerData: item.customer_data
+ customerData: item.customer_data,
+ override: item.override,
+ overrideAt: item.override_at,
+ overrideBy: item.override_by
})))
}
@@ -46,8 +48,10 @@ const getCustomInfoRequestForCustomer = (customerId, infoRequestId) => {
return {
customerId: item.customer_id,
infoRequestId: item.info_request_id,
- approved: item.approved,
- customerData: item.customer_data
+ customerData: item.customer_data,
+ override: item.override,
+ overrideAt: item.override_at,
+ overrideBy: item.override_by
}
})
}
@@ -61,8 +65,10 @@ const batchGetAllCustomInfoRequestsForCustomer = (customerIds) => {
return items.map(item => ({
customerId: item.customer_id,
infoRequestId: item.info_request_id,
- approved: item.approved,
- customerData: item.customer_data
+ customerData: item.customer_data,
+ override: item.override,
+ overrideAt: item.override_at,
+ overrideBy: item.override_by
}))
})
})
@@ -93,9 +99,9 @@ const batchGetCustomInfoRequest = (infoRequestIds) => {
})
}
-const setAuthorizedCustomRequest = (customerId, infoRequestId, isAuthorized) => {
- const sql = `UPDATE customers_custom_info_requests SET approved = $1 WHERE customer_id = $2 AND info_request_id = $3`
- return db.none(sql, [isAuthorized, customerId, infoRequestId]).then(() => true)
+const setAuthorizedCustomRequest = (customerId, infoRequestId, override, token) => {
+ const sql = `UPDATE customers_custom_info_requests SET override = $1, override_by = $2, override_at = now() WHERE customer_id = $3 AND info_request_id = $4`
+ return db.none(sql, [override, token, customerId, infoRequestId]).then(() => true)
}
const setCustomerData = (customerId, infoRequestId, data) => {
@@ -103,7 +109,7 @@ const setCustomerData = (customerId, infoRequestId, data) => {
INSERT INTO customers_custom_info_requests (customer_id, info_request_id, customer_data)
VALUES ($1, $2, $3)
ON CONFLICT (customer_id, info_request_id)
- DO UPDATE SET customer_data = $3, approved = null`
+ DO UPDATE SET customer_data = $3`
return db.none(sql, [customerId, infoRequestId, data])
}
diff --git a/lib/new-config-manager.js b/lib/new-config-manager.js
index 531186ea..0e721736 100644
--- a/lib/new-config-manager.js
+++ b/lib/new-config-manager.js
@@ -1,4 +1,5 @@
const _ = require('lodash/fp')
+const { getCustomInfoRequests } = require('./new-admin/services/customInfoRequests')
const namespaces = {
WALLETS: 'wallets',
@@ -107,22 +108,29 @@ const getGlobalNotifications = config => getNotifications(null, null, config)
const getTriggers = _.get('triggers')
const getTriggersAutomation = config => {
- const defaultAutomation = _.get('triggersConfig_automation')(config)
- const requirements = {
- sanctions: defaultAutomation,
- idCardPhoto: defaultAutomation,
- idCardData: defaultAutomation,
- facephoto: defaultAutomation,
- usSsn: defaultAutomation
- }
+ return getCustomInfoRequests(true)
+ .then(infoRequests => {
+ const defaultAutomation = _.get('triggersConfig_automation')(config)
+ const requirements = {
+ sanctions: defaultAutomation,
+ idCardPhoto: defaultAutomation,
+ idCardData: defaultAutomation,
+ facephoto: defaultAutomation,
+ usSsn: defaultAutomation
+ }
- const overrides = _.get('triggersConfig_overrides')(config)
+ _.forEach(it => {
+ requirements[it.id] = defaultAutomation
+ }, infoRequests)
- const requirementsOverrides = _.reduce((acc, override) => {
- return _.assign(acc, { [override.requirement]: override.automation })
- }, {}, overrides)
+ const overrides = _.get('triggersConfig_overrides')(config)
- return _.assign(requirements, requirementsOverrides)
+ const requirementsOverrides = _.reduce((acc, override) => {
+ return _.assign(acc, { [override.requirement]: override.automation })
+ }, {}, overrides)
+
+ return _.assign(requirements, requirementsOverrides)
+ })
}
const splitGetFirst = _.compose(_.head, _.split('_'))
diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js
index b4089d54..c4568233 100644
--- a/lib/routes/pollingRoutes.js
+++ b/lib/routes/pollingRoutes.js
@@ -73,7 +73,7 @@ function poll (req, res, next) {
const pi = plugins(settings, deviceId)
const hasLightning = checkHasLightning(settings)
- const triggersAutomation = configManager.getTriggersAutomation(settings.config)
+ const triggersAutomationPromise = configManager.getTriggersAutomation(settings.config)
const triggersPromise = buildTriggers(configManager.getTriggers(settings.config))
const operatorInfo = configManager.getOperatorInfo(settings.config)
@@ -84,8 +84,8 @@ function poll (req, res, next) {
state.pids[operatorId] = { [deviceId]: { pid, ts: Date.now() } }
- return Promise.all([pi.pollQueries(serialNumber, deviceTime, req.query, machineVersion, machineModel), triggersPromise])
- .then(([results, triggers]) => {
+ return Promise.all([pi.pollQueries(serialNumber, deviceTime, req.query, machineVersion, machineModel), triggersPromise, triggersAutomationPromise])
+ .then(([results, triggers, triggersAutomation]) => {
const cassettes = results.cassettes
const reboot = pid && state.reboots?.[operatorId]?.[deviceId] === pid
diff --git a/migrations/1642518884925-manual-custom-info-requests.js b/migrations/1642518884925-manual-custom-info-requests.js
new file mode 100644
index 00000000..9a912c6d
--- /dev/null
+++ b/migrations/1642518884925-manual-custom-info-requests.js
@@ -0,0 +1,16 @@
+var db = require('./db')
+
+exports.up = function (next) {
+ var sql = [
+ `ALTER TABLE customers_custom_info_requests DROP COLUMN approved`,
+ `ALTER TABLE customers_custom_info_requests ADD COLUMN override verification_type NOT NULL DEFAULT 'automatic'`,
+ `ALTER TABLE customers_custom_info_requests ADD COLUMN override_by UUID REFERENCES users(id)`,
+ `ALTER TABLE customers_custom_info_requests ADD COLUMN override_at TIMESTAMPTZ`
+ ]
+
+ db.multi(sql, next)
+}
+
+exports.down = function (next) {
+ next()
+}
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerData.js b/new-lamassu-admin/src/pages/Customers/CustomerData.js
index 18a53cac..dde30bef 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerData.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerData.js
@@ -324,7 +324,6 @@ const CustomerData = ({
]
R.forEach(it => {
- console.log('it', it)
customRequirements.push({
fields: [
{
@@ -336,12 +335,13 @@ const CustomerData = ({
],
title: it.customInfoRequest.customRequest.name,
titleIcon: ,
+ state: R.path(['override'])(it),
authorize: () =>
authorizeCustomRequest({
variables: {
customerId: it.customerId,
infoRequestId: it.customInfoRequest.id,
- isAuthorized: true
+ override: OVERRIDE_AUTHORIZED
}
}),
reject: () =>
@@ -349,7 +349,7 @@ const CustomerData = ({
variables: {
customerId: it.customerId,
infoRequestId: it.customInfoRequest.id,
- isAuthorized: false
+ override: OVERRIDE_REJECTED
}
}),
save: values => {
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
index 91b076ee..b127b5ec 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
@@ -95,7 +95,9 @@ const GET_CUSTOMER = gql`
}
customInfoRequests {
customerId
- approved
+ override
+ overrideBy
+ overrideAt
customerData
customInfoRequest {
id
@@ -180,12 +182,12 @@ const SET_AUTHORIZED_REQUEST = gql`
mutation setAuthorizedCustomRequest(
$customerId: ID!
$infoRequestId: ID!
- $isAuthorized: Boolean!
+ $override: String!
) {
setAuthorizedCustomRequest(
customerId: $customerId
infoRequestId: $infoRequestId
- isAuthorized: $isAuthorized
+ override: $override
)
}
`
diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomInfoRequestsData.js b/new-lamassu-admin/src/pages/Customers/components/CustomInfoRequestsData.js
index ced31cef..d8ca647d 100644
--- a/new-lamassu-admin/src/pages/Customers/components/CustomInfoRequestsData.js
+++ b/new-lamassu-admin/src/pages/Customers/components/CustomInfoRequestsData.js
@@ -53,12 +53,12 @@ const SET_AUTHORIZED_REQUEST = gql`
mutation setAuthorizedCustomRequest(
$customerId: ID!
$infoRequestId: ID!
- $isAuthorized: Boolean!
+ $override: String!
) {
setAuthorizedCustomRequest(
customerId: $customerId
infoRequestId: $infoRequestId
- isAuthorized: $isAuthorized
+ override: $override
)
}
`
diff --git a/new-lamassu-admin/src/pages/Triggers/components/AdvancedTriggers.js b/new-lamassu-admin/src/pages/Triggers/components/AdvancedTriggers.js
index cb58bfa2..038a6825 100644
--- a/new-lamassu-admin/src/pages/Triggers/components/AdvancedTriggers.js
+++ b/new-lamassu-admin/src/pages/Triggers/components/AdvancedTriggers.js
@@ -28,13 +28,34 @@ const GET_INFO = gql`
}
`
+const GET_CUSTOM_REQUESTS = gql`
+ query customInfoRequests {
+ customInfoRequests {
+ id
+ customRequest
+ enabled
+ }
+ }
+`
+
const AdvancedTriggersSettings = memo(() => {
const SCREEN_KEY = namespaces.TRIGGERS
const [error, setError] = useState(null)
const [isEditingDefault, setEditingDefault] = useState(false)
const [isEditingOverrides, setEditingOverrides] = useState(false)
- const { data } = useQuery(GET_INFO)
+ const { data, loading: configLoading } = useQuery(GET_INFO)
+ const { data: customInfoReqData, loading: customInfoLoading } = useQuery(
+ GET_CUSTOM_REQUESTS
+ )
+
+ const customInfoRequests =
+ R.path(['customInfoRequests'])(customInfoReqData) ?? []
+ const enabledCustomInfoRequests = R.filter(R.propEq('enabled', true))(
+ customInfoRequests
+ )
+
+ const loading = configLoading || customInfoLoading
const [saveConfig] = useMutation(SAVE_CONFIG, {
refetchQueries: () => ['getData'],
@@ -67,42 +88,47 @@ const AdvancedTriggersSettings = memo(() => {
const onEditingOverrides = (it, editing) => setEditingOverrides(editing)
return (
- <>
-
-
- >
+ !loading && (
+ <>
+
+
+ >
+ )
)
})
diff --git a/new-lamassu-admin/src/pages/Triggers/components/helper.js b/new-lamassu-admin/src/pages/Triggers/components/helper.js
index 0737bca8..e7c660df 100644
--- a/new-lamassu-admin/src/pages/Triggers/components/helper.js
+++ b/new-lamassu-admin/src/pages/Triggers/components/helper.js
@@ -4,18 +4,29 @@ import * as Yup from 'yup'
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
import { getView } from 'src/pages/Triggers/helper'
-const advancedRequirementOptions = [
- { display: 'Sanctions', code: 'sanctions' },
- { display: 'ID card image', code: 'idCardPhoto' },
- { display: 'ID data', code: 'idCardData' },
- { display: 'Customer camera', code: 'facephoto' },
- { display: 'US SSN', code: 'usSsn' }
-]
+const buildAdvancedRequirementOptions = customInfoRequests => {
+ const base = [
+ { display: 'Sanctions', code: 'sanctions' },
+ { display: 'ID card image', code: 'idCardPhoto' },
+ { display: 'ID data', code: 'idCardData' },
+ { display: 'Customer camera', code: 'facephoto' },
+ { display: 'US SSN', code: 'usSsn' }
+ ]
-const displayRequirement = code => {
+ const custom = R.map(it => ({
+ display: it.customRequest.name,
+ code: it.id
+ }))(customInfoRequests)
+
+ return R.concat(base, custom)
+}
+
+const displayRequirement = (code, customInfoRequests) => {
return R.prop(
'display',
- R.find(R.propEq('code', code))(advancedRequirementOptions)
+ R.find(R.propEq('code', code))(
+ buildAdvancedRequirementOptions(customInfoRequests)
+ )
)
}
@@ -29,7 +40,7 @@ const defaultSchema = Yup.object().shape({
.required()
})
-const getOverridesSchema = values => {
+const getOverridesSchema = (values, customInfoRequests) => {
return Yup.object().shape({
id: Yup.string()
.label('Requirement')
@@ -40,7 +51,8 @@ const getOverridesSchema = values => {
if (R.find(R.propEq('requirement', requirement))(values)) {
return this.createError({
message: `Requirement ${displayRequirement(
- requirement
+ requirement,
+ customInfoRequests
)} already overriden`
})
}
@@ -84,17 +96,20 @@ const getDefaultSettings = () => {
]
}
-const getOverrides = () => {
+const getOverrides = customInfoRequests => {
return [
{
name: 'requirement',
header: 'Requirement',
width: 196,
size: 'sm',
- view: getView(advancedRequirementOptions, 'display'),
+ view: getView(
+ buildAdvancedRequirementOptions(customInfoRequests),
+ 'display'
+ ),
input: Autocomplete,
inputProps: {
- options: advancedRequirementOptions,
+ options: buildAdvancedRequirementOptions(customInfoRequests),
labelProp: 'display',
valueProp: 'code'
}
From 556d8433fa209293989ce85a8a0ee1559b62a7d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Wed, 5 Jan 2022 17:40:15 +0000
Subject: [PATCH 48/51] feat: add testing customer toggle
---
lib/customers.js | 22 ++-
.../graphql/resolvers/customer.resolver.js | 6 +-
.../graphql/resolvers/transaction.resolver.js | 8 +-
lib/new-admin/graphql/types/customer.type.js | 3 +
.../graphql/types/transaction.type.js | 4 +-
lib/new-admin/services/transactions.js | 3 +
.../1641394367865-testing-customer-toggle.js | 13 ++
.../src/components/LogsDownloaderPopper.js | 6 +-
.../src/pages/Analytics/Analytics.js | 8 +-
.../src/pages/Customers/CustomerProfile.js | 176 +++++++++++-------
.../pages/Customers/CustomerProfile.styles.js | 21 ++-
.../components/CustomerSidebar.styles.js | 3 +-
.../SystemPerformance/SystemPerformance.js | 8 +-
.../src/pages/Transactions/Transactions.js | 2 +
14 files changed, 191 insertions(+), 92 deletions(-)
create mode 100644 migrations/1641394367865-testing-customer-toggle.js
diff --git a/lib/customers.js b/lib/customers.js
index 1b03b9ea..cf6f30fb 100644
--- a/lib/customers.js
+++ b/lib/customers.js
@@ -632,7 +632,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
phone, sms_override, id_card_data, id_card_data_override, id_card_data_expiration,
id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at,
sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
- fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, custom_fields, notes
+ fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, custom_fields, notes, is_test_customer
FROM (
SELECT c.id, c.authorized_override,
greatest(0, date_part('day', c.suspended_until - NOW())) AS days_suspended,
@@ -640,7 +640,7 @@ function getCustomersList (phone = null, name = null, address = null, id = null)
c.front_camera_path, c.front_camera_override,
c.phone, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions,
- c.sanctions_at, c.sanctions_override, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
+ c.sanctions_at, c.sanctions_override, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
row_number() OVER (partition by c.id order by t.created desc) AS rn,
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (partition by c.id) AS total_txs,
coalesce(sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (partition by c.id), 0) AS total_spent, ccf.custom_fields
@@ -687,7 +687,7 @@ function getCustomerById (id) {
phone, sms_override, id_card_data_at, id_card_data, id_card_data_override, id_card_data_expiration,
id_card_photo_path, id_card_photo_at, id_card_photo_override, us_ssn_at, us_ssn, us_ssn_override, sanctions, sanctions_at,
sanctions_override, total_txs, total_spent, LEAST(created, last_transaction) AS last_active, fiat AS last_tx_fiat,
- fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes
+ fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, subscriber_info, custom_fields, notes, is_test_customer
FROM (
SELECT c.id, c.authorized_override,
greatest(0, date_part('day', c.suspended_until - now())) AS days_suspended,
@@ -695,7 +695,7 @@ function getCustomerById (id) {
c.front_camera_path, c.front_camera_override, c.front_camera_at,
c.phone, c.sms_override, c.id_card_data, c.id_card_data_at, c.id_card_data_override, c.id_card_data_expiration,
c.id_card_photo_path, c.id_card_photo_at, c.id_card_photo_override, c.us_ssn, c.us_ssn_at, c.us_ssn_override, c.sanctions,
- c.sanctions_at, c.sanctions_override, c.subscriber_info, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
+ c.sanctions_at, c.sanctions_override, c.subscriber_info, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
row_number() OVER (PARTITION BY c.id ORDER BY t.created DESC) AS rn,
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (PARTITION BY c.id) AS total_txs,
sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (PARTITION BY c.id) AS total_spent, ccf.custom_fields
@@ -1023,6 +1023,16 @@ function getCustomInfoRequestsData (customer) {
return db.any(sql, [customer.id]).then(res => _.set('custom_info_request_data', res, customer))
}
+function enableTestCustomer (customerId) {
+ const sql = `UPDATE customers SET is_test_customer=true WHERE id=$1`
+ return db.none(sql, [customerId])
+}
+
+function disableTestCustomer (customerId) {
+ const sql = `UPDATE customers SET is_test_customer=false WHERE id=$1`
+ return db.none(sql, [customerId])
+}
+
module.exports = {
add,
get,
@@ -1041,5 +1051,7 @@ module.exports = {
edit,
deleteEditedData,
updateEditedPhoto,
- updateTxCustomerPhoto
+ updateTxCustomerPhoto,
+ enableTestCustomer,
+ disableTestCustomer
}
diff --git a/lib/new-admin/graphql/resolvers/customer.resolver.js b/lib/new-admin/graphql/resolvers/customer.resolver.js
index 9edbf762..19b54f79 100644
--- a/lib/new-admin/graphql/resolvers/customer.resolver.js
+++ b/lib/new-admin/graphql/resolvers/customer.resolver.js
@@ -50,7 +50,11 @@ const resolvers = {
deleteCustomerNote: (...[, { noteId }]) => {
return customerNotes.deleteCustomerNote(noteId)
},
- createCustomer: (...[, { phoneNumber }]) => customers.add({ phone: phoneNumber })
+ createCustomer: (...[, { phoneNumber }]) => customers.add({ phone: phoneNumber }),
+ enableTestCustomer: (...[, { customerId }]) =>
+ customers.enableTestCustomer(customerId),
+ disableTestCustomer: (...[, { customerId }]) =>
+ customers.disableTestCustomer(customerId)
}
}
diff --git a/lib/new-admin/graphql/resolvers/transaction.resolver.js b/lib/new-admin/graphql/resolvers/transaction.resolver.js
index 3dc5a399..96bb406e 100644
--- a/lib/new-admin/graphql/resolvers/transaction.resolver.js
+++ b/lib/new-admin/graphql/resolvers/transaction.resolver.js
@@ -30,10 +30,10 @@ const resolvers = {
isAnonymous: parent => (parent.customerId === anonymous.uuid)
},
Query: {
- transactions: (...[, { from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status }]) =>
- transactions.batch(from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status),
- transactionsCsv: (...[, { from, until, limit, offset, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, timezone, simplified }]) =>
- transactions.batch(from, until, limit, offset, null, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, simplified)
+ transactions: (...[, { from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, excludeTestingCustomers }]) =>
+ transactions.batch(from, until, limit, offset, deviceId, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, excludeTestingCustomers),
+ transactionsCsv: (...[, { from, until, limit, offset, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, timezone, excludeTestingCustomers, simplified }]) =>
+ transactions.batch(from, until, limit, offset, null, txClass, machineName, customerName, fiatCode, cryptoCode, toAddress, status, excludeTestingCustomers, simplified)
.then(data => parseAsync(logDateFormat(timezone, data, ['created', 'sendTime']), { fields: txLogFields })),
transactionCsv: (...[, { id, txClass, timezone }]) =>
transactions.getTx(id, txClass).then(data =>
diff --git a/lib/new-admin/graphql/types/customer.type.js b/lib/new-admin/graphql/types/customer.type.js
index f302c263..bf099647 100644
--- a/lib/new-admin/graphql/types/customer.type.js
+++ b/lib/new-admin/graphql/types/customer.type.js
@@ -36,6 +36,7 @@ const typeDef = gql`
customFields: [CustomerCustomField]
customInfoRequests: [CustomRequestData]
notes: [CustomerNote]
+ isTestCustomer: Boolean
}
input CustomerInput {
@@ -104,6 +105,8 @@ const typeDef = gql`
editCustomerNote(noteId: ID!, newContent: String!): Boolean @auth
deleteCustomerNote(noteId: ID!): Boolean @auth
createCustomer(phoneNumber: String): Customer @auth
+ enableTestCustomer(customerId: ID!): Boolean @auth
+ disableTestCustomer(customerId: ID!): Boolean @auth
}
`
diff --git a/lib/new-admin/graphql/types/transaction.type.js b/lib/new-admin/graphql/types/transaction.type.js
index c121d0bd..a0212e91 100644
--- a/lib/new-admin/graphql/types/transaction.type.js
+++ b/lib/new-admin/graphql/types/transaction.type.js
@@ -55,8 +55,8 @@ const typeDef = gql`
}
type Query {
- transactions(from: Date, until: Date, limit: Int, offset: Int, deviceId: ID, txClass: String, machineName: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String): [Transaction] @auth
- transactionsCsv(from: Date, until: Date, limit: Int, offset: Int, txClass: String, machineName: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, timezone: String, simplified: Boolean): String @auth
+ transactions(from: Date, until: Date, limit: Int, offset: Int, deviceId: ID, txClass: String, machineName: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, excludeTestingCustomers: Boolean): [Transaction] @auth
+ transactionsCsv(from: Date, until: Date, limit: Int, offset: Int, txClass: String, machineName: String, customerName: String, fiatCode: String, cryptoCode: String, toAddress: String, status: String, timezone: String, excludeTestingCustomers: Boolean, simplified: Boolean): String @auth
transactionCsv(id: ID, txClass: String, timezone: String): String @auth
txAssociatedDataCsv(id: ID, txClass: String, timezone: String): String @auth
transactionFilters: [Filter] @auth
diff --git a/lib/new-admin/services/transactions.js b/lib/new-admin/services/transactions.js
index 494630f9..e420355a 100644
--- a/lib/new-admin/services/transactions.js
+++ b/lib/new-admin/services/transactions.js
@@ -39,6 +39,7 @@ function batch (
cryptoCode = null,
toAddress = null,
status = null,
+ excludeTestingCustomers = false,
simplified = false
) {
const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames)
@@ -67,6 +68,7 @@ function batch (
AND ($11 is null or txs.crypto_code = $11)
AND ($12 is null or txs.to_address = $12)
AND ($13 is null or txs.txStatus = $13)
+ ${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
AND (fiat > 0)
ORDER BY created DESC limit $4 offset $5`
@@ -98,6 +100,7 @@ function batch (
AND ($11 is null or txs.crypto_code = $11)
AND ($12 is null or txs.to_address = $12)
AND ($13 is null or txs.txStatus = $13)
+ ${excludeTestingCustomers ? `AND c.is_test_customer is false` : ``}
AND (fiat > 0)
ORDER BY created DESC limit $4 offset $5`
diff --git a/migrations/1641394367865-testing-customer-toggle.js b/migrations/1641394367865-testing-customer-toggle.js
new file mode 100644
index 00000000..174aaa58
--- /dev/null
+++ b/migrations/1641394367865-testing-customer-toggle.js
@@ -0,0 +1,13 @@
+var db = require('./db')
+
+exports.up = function (next) {
+ var sql = [
+ `ALTER TABLE customers ADD COLUMN is_test_customer BOOLEAN DEFAULT false`,
+ ]
+
+ db.multi(sql, next)
+}
+
+exports.down = function (next) {
+ next()
+}
diff --git a/new-lamassu-admin/src/components/LogsDownloaderPopper.js b/new-lamassu-admin/src/components/LogsDownloaderPopper.js
index 9c02760a..f41dde88 100644
--- a/new-lamassu-admin/src/components/LogsDownloaderPopper.js
+++ b/new-lamassu-admin/src/components/LogsDownloaderPopper.js
@@ -181,7 +181,8 @@ const LogsDownloaderPopover = ({
fetchLogs({
variables: {
...args,
- simplified: selectedAdvancedRadio === SIMPLIFIED
+ simplified: selectedAdvancedRadio === SIMPLIFIED,
+ excludeTestingCustomers: true
}
})
}
@@ -196,7 +197,8 @@ const LogsDownloaderPopover = ({
...args,
from: range.from,
until: range.until,
- simplified: selectedAdvancedRadio === SIMPLIFIED
+ simplified: selectedAdvancedRadio === SIMPLIFIED,
+ excludeTestingCustomers: true
}
})
}
diff --git a/new-lamassu-admin/src/pages/Analytics/Analytics.js b/new-lamassu-admin/src/pages/Analytics/Analytics.js
index 8b00c2fc..4d1d6114 100644
--- a/new-lamassu-admin/src/pages/Analytics/Analytics.js
+++ b/new-lamassu-admin/src/pages/Analytics/Analytics.js
@@ -42,8 +42,8 @@ const TIME_OPTIONS = {
}
const GET_TRANSACTIONS = gql`
- query transactions($limit: Int, $from: Date, $until: Date) {
- transactions(limit: $limit, from: $from, until: $until) {
+ query transactions($excludeTestingCustomers: Boolean) {
+ transactions(excludeTestingCustomers: $excludeTestingCustomers) {
id
txClass
txHash
@@ -116,7 +116,9 @@ const OverviewEntry = ({ label, value, oldValue, currency }) => {
const Analytics = () => {
const classes = useStyles()
- const { data: txResponse, loading: txLoading } = useQuery(GET_TRANSACTIONS)
+ const { data: txResponse, loading: txLoading } = useQuery(GET_TRANSACTIONS, {
+ variables: { excludeTestingCustomers: true }
+ })
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
const [representing, setRepresenting] = useState(REPRESENTING_OPTIONS[0])
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
index 59d0202c..5e2de3c1 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.js
@@ -7,6 +7,7 @@ import React, { memo, useState } from 'react'
import { useHistory, useParams } from 'react-router-dom'
import { ActionButton } from 'src/components/buttons'
+import { Switch } from 'src/components/inputs'
import { Label1, Label2 } from 'src/components/typography'
import {
OVERRIDE_AUTHORIZED,
@@ -67,6 +68,7 @@ const GET_CUSTOMER = gql`
lastTxClass
daysSuspended
isSuspended
+ isTestCustomer
customFields {
id
label
@@ -231,6 +233,18 @@ const EDIT_NOTE = gql`
}
`
+const ENABLE_TEST_CUSTOMER = gql`
+ mutation enableTestCustomer($customerId: ID!) {
+ enableTestCustomer(customerId: $customerId)
+ }
+`
+
+const DISABLE_TEST_CUSTOMER = gql`
+ mutation disableTestCustomer($customerId: ID!) {
+ disableTestCustomer(customerId: $customerId)
+ }
+`
+
const GET_DATA = gql`
query getData {
config
@@ -351,6 +365,16 @@ const CustomerProfile = memo(() => {
})
}
+ const [enableTestCustomer] = useMutation(ENABLE_TEST_CUSTOMER, {
+ variables: { customerId },
+ onCompleted: () => getCustomer()
+ })
+
+ const [disableTestCustomer] = useMutation(DISABLE_TEST_CUSTOMER, {
+ variables: { customerId },
+ onCompleted: () => getCustomer()
+ })
+
const updateCustomer = it =>
setCustomer({
variables: {
@@ -478,85 +502,101 @@ const CustomerProfile = memo(() => {
{!loading && !customerData.isAnonymous && (
-
+ <>
+
code === clickedItem}
+ onClick={onClickSidebarItem}
+ />
- code === clickedItem}
- onClick={onClickSidebarItem}
- />
-
- Actions
-
-
setWizard(true)}>
- {`Manual data entry`}
-
- {
- // TODO: Enable for next release
- /*
{}}>
- {`Add individual discount`}
- */
- }
- {isSuspended && (
+
Actions
+
setWizard(true)}>
+ {`Manual data entry`}
+
+ {/*
{}}>
+ {`Add individual discount`}
+ */}
+ {isSuspended && (
+
+ updateCustomer({
+ suspendedUntil: null
+ })
+ }>
+ {`Unsuspend customer`}
+
+ )}
+
updateCustomer({
- suspendedUntil: null
+ authorizedOverride: blocked
+ ? OVERRIDE_AUTHORIZED
+ : OVERRIDE_REJECTED
})
}>
- {`Unsuspend customer`}
+ {`${blocked ? 'Authorize' : 'Block'} customer`}
- )}
-
- updateCustomer({
- authorizedOverride: blocked
- ? OVERRIDE_AUTHORIZED
- : OVERRIDE_REJECTED
- })
- }>
- {`${blocked ? 'Authorize' : 'Block'} customer`}
-
-
- setCustomer({
- variables: {
- customerId,
- customerInput: {
- subscriberInfo: true
+
+ setCustomer({
+ variables: {
+ customerId,
+ customerInput: {
+ subscriberInfo: true
+ }
}
- }
- })
- }>
- {`Retrieve information`}
-
+ })
+ }>
+ {`Retrieve information`}
+
+
-
+
+
+ {`Special user status`}
+
+
+
+
+ R.path(['isTestCustomer'])(customerData)
+ ? disableTestCustomer()
+ : enableTestCustomer()
+ }
+ />
+ {`Test user`}
+
+
+
+ >
)}
diff --git a/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js b/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js
index ee9a7e82..a486abee 100644
--- a/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js
+++ b/new-lamassu-admin/src/pages/Customers/CustomerProfile.styles.js
@@ -1,4 +1,4 @@
-import { comet } from 'src/styling/variables'
+import { comet, subheaderColor } from 'src/styling/variables'
export default {
labelLink: {
@@ -34,6 +34,23 @@ export default {
width: 1100
},
leftSidePanel: {
- width: 300
+ width: 300,
+ '& > *': {
+ marginBottom: 25
+ },
+ '& > *:last-child': {
+ marginBottom: 0
+ },
+ '& > *:first-child': {
+ marginBottom: 50
+ }
+ },
+ userStatusAction: {
+ display: 'flex',
+ flexDirection: 'row',
+ alignItems: 'center',
+ backgroundColor: subheaderColor,
+ borderRadius: 8,
+ padding: [[0, 5]]
}
}
diff --git a/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.styles.js b/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.styles.js
index 87ef5087..9485c7f4 100644
--- a/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.styles.js
+++ b/new-lamassu-admin/src/pages/Customers/components/CustomerSidebar.styles.js
@@ -11,8 +11,7 @@ export default {
backgroundColor: sidebarColor,
width: 219,
flexDirection: 'column',
- borderRadius: 5,
- marginBottom: 50
+ borderRadius: 5
},
link: {
alignItems: 'center',
diff --git a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js
index 29b3dca1..d437b6e5 100644
--- a/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js
+++ b/new-lamassu-admin/src/pages/Dashboard/SystemPerformance/SystemPerformance.js
@@ -52,8 +52,8 @@ const ranges = {
}
const GET_DATA = gql`
- query getData {
- transactions {
+ query getData($excludeTestingCustomers: Boolean) {
+ transactions(excludeTestingCustomers: $excludeTestingCustomers) {
fiatCode
fiat
cashInFee
@@ -78,7 +78,9 @@ const reducer = (acc, it) =>
const SystemPerformance = () => {
const classes = useStyles()
const [selectedRange, setSelectedRange] = useState('Day')
- const { data, loading } = useQuery(GET_DATA)
+ const { data, loading } = useQuery(GET_DATA, {
+ variables: { excludeTestingCustomers: true }
+ })
const fiatLocale = fromNamespace('locale')(data?.config).fiatCurrency
const timezone = fromNamespace('locale')(data?.config).timezone
diff --git a/new-lamassu-admin/src/pages/Transactions/Transactions.js b/new-lamassu-admin/src/pages/Transactions/Transactions.js
index 009335c3..546f7476 100644
--- a/new-lamassu-admin/src/pages/Transactions/Transactions.js
+++ b/new-lamassu-admin/src/pages/Transactions/Transactions.js
@@ -40,6 +40,7 @@ const GET_TRANSACTIONS_CSV = gql`
$from: Date
$until: Date
$timezone: String
+ $excludeTestingCustomers: Boolean
) {
transactionsCsv(
simplified: $simplified
@@ -47,6 +48,7 @@ const GET_TRANSACTIONS_CSV = gql`
from: $from
until: $until
timezone: $timezone
+ excludeTestingCustomers: $excludeTestingCustomers
)
}
`
From a21bb1cbd5ec29315a46395e6dafcc23c34c10ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9rgio=20Salgado?=
Date: Mon, 17 Jan 2022 23:22:30 +0000
Subject: [PATCH 49/51] fix: limit transactions from analytics page to the last
2 months
---
.../1641394367865-testing-customer-toggle.js | 2 +-
.../src/pages/Analytics/Analytics.js | 20 ++++++++++++++++---
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/migrations/1641394367865-testing-customer-toggle.js b/migrations/1641394367865-testing-customer-toggle.js
index 174aaa58..a8b8d522 100644
--- a/migrations/1641394367865-testing-customer-toggle.js
+++ b/migrations/1641394367865-testing-customer-toggle.js
@@ -2,7 +2,7 @@ var db = require('./db')
exports.up = function (next) {
var sql = [
- `ALTER TABLE customers ADD COLUMN is_test_customer BOOLEAN DEFAULT false`,
+ `ALTER TABLE customers ADD COLUMN is_test_customer BOOLEAN NOT NULL DEFAULT false`,
]
db.multi(sql, next)
diff --git a/new-lamassu-admin/src/pages/Analytics/Analytics.js b/new-lamassu-admin/src/pages/Analytics/Analytics.js
index 4d1d6114..936db2ba 100644
--- a/new-lamassu-admin/src/pages/Analytics/Analytics.js
+++ b/new-lamassu-admin/src/pages/Analytics/Analytics.js
@@ -2,6 +2,8 @@ import { useQuery } from '@apollo/react-hooks'
import { Box } from '@material-ui/core'
import { makeStyles } from '@material-ui/core/styles'
import classnames from 'classnames'
+import { endOfToday } from 'date-fns'
+import { subDays } from 'date-fns/fp'
import gql from 'graphql-tag'
import * as R from 'ramda'
import React, { useState } from 'react'
@@ -42,8 +44,16 @@ const TIME_OPTIONS = {
}
const GET_TRANSACTIONS = gql`
- query transactions($excludeTestingCustomers: Boolean) {
- transactions(excludeTestingCustomers: $excludeTestingCustomers) {
+ query transactions(
+ $from: Date
+ $until: Date
+ $excludeTestingCustomers: Boolean
+ ) {
+ transactions(
+ from: $from
+ until: $until
+ excludeTestingCustomers: $excludeTestingCustomers
+ ) {
id
txClass
txHash
@@ -117,7 +127,11 @@ const Analytics = () => {
const classes = useStyles()
const { data: txResponse, loading: txLoading } = useQuery(GET_TRANSACTIONS, {
- variables: { excludeTestingCustomers: true }
+ variables: {
+ from: subDays(65, endOfToday()),
+ until: endOfToday(),
+ excludeTestingCustomers: true
+ }
})
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
From 3d8281fb7349f7e69ba491273c6825104fdb1676 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Wed, 19 Jan 2022 15:05:34 +0100
Subject: [PATCH 50/51] fix: guard against null `expirationDate`
fix: NaN age
---
.../src/pages/Transactions/DetailsCard.js | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
index e33ca0a7..73051ac8 100644
--- a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
+++ b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
@@ -116,16 +116,27 @@ const DetailsRow = ({ it: tx, timezone }) => {
const exchangeRate = BigNumber(fiat / crypto).toFormat(2)
const displayExRate = `1 ${tx.cryptoCode} = ${exchangeRate} ${tx.fiatCode}`
+ const parseDateString = d => parse(new Date(), 'yyyyMMdd', d)
+
const customer = tx.customerIdCardData && {
name: `${onlyFirstToUpper(
tx.customerIdCardData.firstName
)} ${onlyFirstToUpper(tx.customerIdCardData.lastName)}`,
- age: differenceInYears(tx.customerIdCardData.dateOfBirth, new Date()),
+ age:
+ (tx.customerIdCardData.dateOfBirth &&
+ differenceInYears(
+ parseDateString(tx.customerIdCardData.dateOfBirth),
+ new Date()
+ )) ??
+ '',
country: tx.customerIdCardData.country,
idCardNumber: tx.customerIdCardData.documentNumber,
- idCardExpirationDate: format('yyyy-MM-dd')(
- parse(new Date(), 'yyyyMMdd', tx.customerIdCardData.expirationDate)
- )
+ idCardExpirationDate:
+ (tx.customerIdCardData.expirationDate &&
+ format('yyyy-MM-dd')(
+ parseDateString(tx.customerIdCardData.expirationDate)
+ )) ??
+ ''
}
const from = sub({ minutes: MINUTES_OFFSET }, tx.created)
From e89b1c233a6cec9891eaca0efb50e1bbb3ed6263 Mon Sep 17 00:00:00 2001
From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com>
Date: Wed, 19 Jan 2022 15:11:18 +0100
Subject: [PATCH 51/51] chore: eta reduction
---
new-lamassu-admin/src/pages/Transactions/DetailsCard.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
index 73051ac8..c960a3ee 100644
--- a/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
+++ b/new-lamassu-admin/src/pages/Transactions/DetailsCard.js
@@ -116,7 +116,7 @@ const DetailsRow = ({ it: tx, timezone }) => {
const exchangeRate = BigNumber(fiat / crypto).toFormat(2)
const displayExRate = `1 ${tx.cryptoCode} = ${exchangeRate} ${tx.fiatCode}`
- const parseDateString = d => parse(new Date(), 'yyyyMMdd', d)
+ const parseDateString = parse(new Date(), 'yyyyMMdd')
const customer = tx.customerIdCardData && {
name: `${onlyFirstToUpper(