feat: advanced options screen triggers
This commit is contained in:
parent
c4a26a7f37
commit
5ed84b7cdb
6 changed files with 337 additions and 65 deletions
|
|
@ -0,0 +1,16 @@
|
|||
const db = require('./db')
|
||||
|
||||
|
||||
exports.up = function (next) {
|
||||
|
||||
const sql2 = [`insert into user_config (type, data, valid, schema_version) values ('config', '{"config":{
|
||||
"triggersConfig_expirationTime": "Forever",
|
||||
"triggersConfig_automation": "Automatic"
|
||||
} }', true, 2)`]
|
||||
|
||||
db.multi(sql2, next)
|
||||
}
|
||||
|
||||
exports.down = function (next) {
|
||||
next()
|
||||
}
|
||||
|
|
@ -11,10 +11,13 @@ import { Table as EditableTable } from 'src/components/editableTable'
|
|||
import { Switch } from 'src/components/inputs'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
import { P, Label2, H2 } from 'src/components/typography'
|
||||
import { ReactComponent as ReverseListingViewIcon } from 'src/styling/icons/circle buttons/listing-view/white.svg'
|
||||
import { ReactComponent as ListingViewIcon } from 'src/styling/icons/circle buttons/listing-view/zodiac.svg'
|
||||
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
import styles from './Triggers.styles'
|
||||
import Wizard from './Wizard'
|
||||
import AdvancedTriggers from './components/AdvancedTriggers'
|
||||
import { Schema, getElements, sortBy, fromServer, toServer } from './helper'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
|
@ -34,6 +37,7 @@ const GET_INFO = gql`
|
|||
const Triggers = () => {
|
||||
const classes = useStyles()
|
||||
const [wizard, setWizard] = useState(false)
|
||||
const [advancedSettings, setAdvancedSettings] = useState(false)
|
||||
|
||||
const { data, loading } = useQuery(GET_INFO)
|
||||
const triggers = fromServer(data?.config?.triggers ?? [])
|
||||
|
|
@ -74,72 +78,96 @@ const Triggers = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<TitleSection title="Compliance Triggers" className={classes.tableWidth}>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="end"
|
||||
mr="-5px">
|
||||
<P>Reject reused addresses</P>
|
||||
<Switch
|
||||
checked={rejectAddressReuse}
|
||||
onChange={event => {
|
||||
addressReuseSave({ rejectAddressReuse: event.target.checked })
|
||||
}}
|
||||
value={rejectAddressReuse}
|
||||
/>
|
||||
<Label2 className={classes.switchLabel}>
|
||||
{rejectAddressReuse ? 'On' : 'Off'}
|
||||
</Label2>
|
||||
<Tooltip width={304}>
|
||||
<P>
|
||||
This option requires a user to scan a different cryptocurrency
|
||||
address if they attempt to scan one that had been previously
|
||||
used for a transaction in your network
|
||||
</P>
|
||||
</Tooltip>
|
||||
<TitleSection
|
||||
title="Compliance Triggers"
|
||||
button={{
|
||||
text: 'Advanced settings',
|
||||
icon: ListingViewIcon,
|
||||
inverseIcon: ReverseListingViewIcon,
|
||||
toggle: setAdvancedSettings
|
||||
}}
|
||||
className={classes.tableWidth}>
|
||||
{!advancedSettings && (
|
||||
<Box display="flex" alignItems="center">
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="end"
|
||||
mr="-5px">
|
||||
<P>Reject reused addresses</P>
|
||||
<Switch
|
||||
checked={rejectAddressReuse}
|
||||
onChange={event => {
|
||||
addressReuseSave({ rejectAddressReuse: event.target.checked })
|
||||
}}
|
||||
value={rejectAddressReuse}
|
||||
/>
|
||||
<Label2 className={classes.switchLabel}>
|
||||
{rejectAddressReuse ? 'On' : 'Off'}
|
||||
</Label2>
|
||||
<Tooltip width={304}>
|
||||
<P>
|
||||
This option requires a user to scan a different cryptocurrency
|
||||
address if they attempt to scan one that had been previously
|
||||
used for a transaction in your network
|
||||
</P>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</TitleSection>
|
||||
<Box
|
||||
marginBottom={2}
|
||||
className={classes.tableWidth}
|
||||
display="flex"
|
||||
justifyContent="flex-end">
|
||||
{!loading && !R.isEmpty(triggers) && (
|
||||
<Link color="primary" onClick={() => setWizard(true)}>
|
||||
+ Add new trigger
|
||||
</Link>
|
||||
)}
|
||||
</Box>
|
||||
<EditableTable
|
||||
data={triggers}
|
||||
name="triggers"
|
||||
enableEdit
|
||||
sortBy={sortBy}
|
||||
groupBy="triggerType"
|
||||
enableDelete
|
||||
error={error?.message}
|
||||
save={save}
|
||||
validationSchema={Schema}
|
||||
elements={getElements(currency, classes)}
|
||||
/>
|
||||
{wizard && (
|
||||
<Wizard
|
||||
currency={currency}
|
||||
error={error?.message}
|
||||
save={add}
|
||||
onClose={() => setWizard(null)}
|
||||
/>
|
||||
</TitleSection>
|
||||
{!advancedSettings && (
|
||||
<>
|
||||
<Box
|
||||
marginBottom={2}
|
||||
className={classes.tableWidth}
|
||||
display="flex"
|
||||
justifyContent="flex-end">
|
||||
{!loading && !R.isEmpty(triggers) && (
|
||||
<Link color="primary" onClick={() => setWizard(true)}>
|
||||
+ Add new trigger
|
||||
</Link>
|
||||
)}
|
||||
</Box>
|
||||
<EditableTable
|
||||
data={triggers}
|
||||
name="triggers"
|
||||
enableEdit
|
||||
sortBy={sortBy}
|
||||
groupBy="triggerType"
|
||||
enableDelete
|
||||
error={error?.message}
|
||||
save={save}
|
||||
validationSchema={Schema}
|
||||
elements={getElements(currency, classes)}
|
||||
/>
|
||||
{wizard && (
|
||||
<Wizard
|
||||
currency={currency}
|
||||
error={error?.message}
|
||||
save={add}
|
||||
onClose={() => setWizard(null)}
|
||||
/>
|
||||
)}
|
||||
{!loading && R.isEmpty(triggers) && (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
flexDirection="column"
|
||||
mt={15}>
|
||||
<H2>
|
||||
It seems there are no active compliance triggers on your network
|
||||
</H2>
|
||||
<Button onClick={() => setWizard(true)}>Add first trigger</Button>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!loading && R.isEmpty(triggers) && (
|
||||
<Box display="flex" alignItems="center" flexDirection="column" mt={15}>
|
||||
<H2>
|
||||
It seems there are no active compliance triggers on your network
|
||||
</H2>
|
||||
<Button onClick={() => setWizard(true)}>Add first trigger</Button>
|
||||
</Box>
|
||||
{advancedSettings && (
|
||||
<AdvancedTriggers
|
||||
error={error}
|
||||
save={saveConfig}
|
||||
data={data}></AdvancedTriggers>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
import { useMutation, useQuery } from '@apollo/react-hooks'
|
||||
import gql from 'graphql-tag'
|
||||
import * as R from 'ramda'
|
||||
import React, { memo, useState } from 'react'
|
||||
|
||||
import { Table as EditableTable } from 'src/components/editableTable'
|
||||
import Section from 'src/components/layout/Section'
|
||||
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
import {
|
||||
defaultSchema,
|
||||
overridesSchema,
|
||||
defaults,
|
||||
overridesDefaults,
|
||||
getDefaultSettings,
|
||||
getOverrides
|
||||
} from './helper'
|
||||
|
||||
const SAVE_CONFIG = gql`
|
||||
mutation Save($config: JSONObject) {
|
||||
saveConfig(config: $config)
|
||||
}
|
||||
`
|
||||
|
||||
const GET_INFO = gql`
|
||||
query getData {
|
||||
config
|
||||
}
|
||||
`
|
||||
|
||||
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 [saveConfig] = useMutation(SAVE_CONFIG, {
|
||||
refetchQueries: () => ['getData'],
|
||||
onError: error => setError(error)
|
||||
})
|
||||
|
||||
const saveDefaults = it => {
|
||||
const newConfig = toNamespace(SCREEN_KEY)(it.triggersConfig[0])
|
||||
setError(null)
|
||||
return saveConfig({
|
||||
variables: { config: newConfig }
|
||||
})
|
||||
}
|
||||
|
||||
const saveOverrides = it => {
|
||||
const config = toNamespace(SCREEN_KEY)(it)
|
||||
setError(null)
|
||||
return saveConfig({ variables: { config } })
|
||||
}
|
||||
|
||||
const requirementsData =
|
||||
data?.config && fromNamespace(SCREEN_KEY)(data?.config)
|
||||
const requirementsDefaults =
|
||||
requirementsData && !R.isEmpty(requirementsData)
|
||||
? requirementsData
|
||||
: defaults
|
||||
const requirementsOverrides = requirementsData?.overrides ?? []
|
||||
|
||||
const onEditingDefault = (it, editing) => setEditingDefault(editing)
|
||||
const onEditingOverrides = (it, editing) => setEditingOverrides(editing)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Section>
|
||||
<EditableTable
|
||||
title="Default requirement settings"
|
||||
error={error?.message}
|
||||
titleLg
|
||||
name="triggersConfig"
|
||||
enableEdit
|
||||
initialValues={requirementsDefaults}
|
||||
save={saveDefaults}
|
||||
validationSchema={defaultSchema}
|
||||
data={R.of(requirementsDefaults)}
|
||||
elements={getDefaultSettings()}
|
||||
setEditing={onEditingDefault}
|
||||
forceDisable={isEditingOverrides}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<EditableTable
|
||||
error={error?.message}
|
||||
title="Overrides"
|
||||
titleLg
|
||||
name="overrides"
|
||||
enableDelete
|
||||
enableEdit
|
||||
enableCreate
|
||||
initialValues={overridesDefaults}
|
||||
save={saveOverrides}
|
||||
validationSchema={overridesSchema}
|
||||
data={requirementsOverrides}
|
||||
elements={getOverrides()}
|
||||
setEditing={onEditingOverrides}
|
||||
forceDisable={isEditingDefault}
|
||||
/>
|
||||
</Section>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
export default AdvancedTriggersSettings
|
||||
116
new-lamassu-admin/src/pages/Triggers/components/helper.js
Normal file
116
new-lamassu-admin/src/pages/Triggers/components/helper.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import * as Yup from 'yup'
|
||||
|
||||
import Autocomplete from 'src/components/inputs/formik/Autocomplete.js'
|
||||
import { getView, requirementOptions } from 'src/pages/Triggers/helper'
|
||||
|
||||
const defaultSchema = Yup.object().shape({
|
||||
expirationTime: Yup.string()
|
||||
.label('Expiration time')
|
||||
.required(),
|
||||
automation: Yup.string()
|
||||
.label('Automation')
|
||||
.matches(/(Manual|Automatic)/)
|
||||
.required()
|
||||
})
|
||||
|
||||
const overridesSchema = Yup.object().shape({
|
||||
id: Yup.string()
|
||||
.label('Requirement')
|
||||
.required(),
|
||||
expirationTime: Yup.string()
|
||||
.label('Expiration time')
|
||||
.required(),
|
||||
automation: Yup.string()
|
||||
.label('Automation')
|
||||
.matches(/(Manual|Automatic)/)
|
||||
.required()
|
||||
})
|
||||
|
||||
const getDefaultSettings = () => {
|
||||
return [
|
||||
{
|
||||
name: 'expirationTime',
|
||||
header: 'Expiration Time',
|
||||
width: 196,
|
||||
size: 'sm',
|
||||
editable: false
|
||||
},
|
||||
{
|
||||
name: 'automation',
|
||||
header: 'Automation',
|
||||
width: 196,
|
||||
size: 'sm',
|
||||
input: Autocomplete,
|
||||
inputProps: {
|
||||
options: [
|
||||
{ code: 'Automatic', display: 'Automatic' },
|
||||
{ code: 'Manual', display: 'Manual' }
|
||||
],
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const getOverrides = () => {
|
||||
return [
|
||||
{
|
||||
name: 'requirement',
|
||||
header: 'Requirement',
|
||||
width: 196,
|
||||
size: 'sm',
|
||||
view: getView(requirementOptions, 'display'),
|
||||
input: Autocomplete,
|
||||
inputProps: {
|
||||
options: requirementOptions,
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'expirationTime',
|
||||
header: 'Expiration Time',
|
||||
width: 196,
|
||||
size: 'sm',
|
||||
editable: false
|
||||
},
|
||||
{
|
||||
name: 'automation',
|
||||
header: 'Automation',
|
||||
width: 196,
|
||||
size: 'sm',
|
||||
input: Autocomplete,
|
||||
inputProps: {
|
||||
options: [
|
||||
{ code: 'Automatic', display: 'Automatic' },
|
||||
{ code: 'Manual', display: 'Manual' }
|
||||
],
|
||||
labelProp: 'display',
|
||||
valueProp: 'code'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const defaults = [
|
||||
{
|
||||
expirationTime: 'Forever',
|
||||
automation: 'Automatic'
|
||||
}
|
||||
]
|
||||
|
||||
const overridesDefaults = {
|
||||
requirement: '',
|
||||
expirationTime: 'Forever',
|
||||
automation: 'Automatic'
|
||||
}
|
||||
|
||||
export {
|
||||
defaultSchema,
|
||||
overridesSchema,
|
||||
defaults,
|
||||
overridesDefaults,
|
||||
getDefaultSettings,
|
||||
getOverrides
|
||||
}
|
||||
|
|
@ -715,5 +715,7 @@ export {
|
|||
requirements,
|
||||
sortBy,
|
||||
fromServer,
|
||||
toServer
|
||||
toServer,
|
||||
getView,
|
||||
requirementOptions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ const namespaces = {
|
|||
COMMISSIONS: 'commissions',
|
||||
RECEIPT: 'receipt',
|
||||
COIN_ATM_RADAR: 'coinAtmRadar',
|
||||
TERMS_CONDITIONS: 'termsConditions'
|
||||
TERMS_CONDITIONS: 'termsConditions',
|
||||
TRIGGERS: 'triggersConfig'
|
||||
}
|
||||
|
||||
const mapKeys = R.curry((fn, obj) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue