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 { Switch } from 'src/components/inputs'
|
||||||
import TitleSection from 'src/components/layout/TitleSection'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { P, Label2, H2 } from 'src/components/typography'
|
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 { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
|
||||||
|
|
||||||
import styles from './Triggers.styles'
|
import styles from './Triggers.styles'
|
||||||
import Wizard from './Wizard'
|
import Wizard from './Wizard'
|
||||||
|
import AdvancedTriggers from './components/AdvancedTriggers'
|
||||||
import { Schema, getElements, sortBy, fromServer, toServer } from './helper'
|
import { Schema, getElements, sortBy, fromServer, toServer } from './helper'
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
@ -34,6 +37,7 @@ const GET_INFO = gql`
|
||||||
const Triggers = () => {
|
const Triggers = () => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [wizard, setWizard] = useState(false)
|
const [wizard, setWizard] = useState(false)
|
||||||
|
const [advancedSettings, setAdvancedSettings] = useState(false)
|
||||||
|
|
||||||
const { data, loading } = useQuery(GET_INFO)
|
const { data, loading } = useQuery(GET_INFO)
|
||||||
const triggers = fromServer(data?.config?.triggers ?? [])
|
const triggers = fromServer(data?.config?.triggers ?? [])
|
||||||
|
|
@ -74,72 +78,96 @@ const Triggers = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TitleSection title="Compliance Triggers" className={classes.tableWidth}>
|
<TitleSection
|
||||||
<Box display="flex" alignItems="center">
|
title="Compliance Triggers"
|
||||||
<Box
|
button={{
|
||||||
display="flex"
|
text: 'Advanced settings',
|
||||||
alignItems="center"
|
icon: ListingViewIcon,
|
||||||
justifyContent="end"
|
inverseIcon: ReverseListingViewIcon,
|
||||||
mr="-5px">
|
toggle: setAdvancedSettings
|
||||||
<P>Reject reused addresses</P>
|
}}
|
||||||
<Switch
|
className={classes.tableWidth}>
|
||||||
checked={rejectAddressReuse}
|
{!advancedSettings && (
|
||||||
onChange={event => {
|
<Box display="flex" alignItems="center">
|
||||||
addressReuseSave({ rejectAddressReuse: event.target.checked })
|
<Box
|
||||||
}}
|
display="flex"
|
||||||
value={rejectAddressReuse}
|
alignItems="center"
|
||||||
/>
|
justifyContent="end"
|
||||||
<Label2 className={classes.switchLabel}>
|
mr="-5px">
|
||||||
{rejectAddressReuse ? 'On' : 'Off'}
|
<P>Reject reused addresses</P>
|
||||||
</Label2>
|
<Switch
|
||||||
<Tooltip width={304}>
|
checked={rejectAddressReuse}
|
||||||
<P>
|
onChange={event => {
|
||||||
This option requires a user to scan a different cryptocurrency
|
addressReuseSave({ rejectAddressReuse: event.target.checked })
|
||||||
address if they attempt to scan one that had been previously
|
}}
|
||||||
used for a transaction in your network
|
value={rejectAddressReuse}
|
||||||
</P>
|
/>
|
||||||
</Tooltip>
|
<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>
|
||||||
</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>
|
</TitleSection>
|
||||||
<EditableTable
|
{!advancedSettings && (
|
||||||
data={triggers}
|
<>
|
||||||
name="triggers"
|
<Box
|
||||||
enableEdit
|
marginBottom={2}
|
||||||
sortBy={sortBy}
|
className={classes.tableWidth}
|
||||||
groupBy="triggerType"
|
display="flex"
|
||||||
enableDelete
|
justifyContent="flex-end">
|
||||||
error={error?.message}
|
{!loading && !R.isEmpty(triggers) && (
|
||||||
save={save}
|
<Link color="primary" onClick={() => setWizard(true)}>
|
||||||
validationSchema={Schema}
|
+ Add new trigger
|
||||||
elements={getElements(currency, classes)}
|
</Link>
|
||||||
/>
|
)}
|
||||||
{wizard && (
|
</Box>
|
||||||
<Wizard
|
<EditableTable
|
||||||
currency={currency}
|
data={triggers}
|
||||||
error={error?.message}
|
name="triggers"
|
||||||
save={add}
|
enableEdit
|
||||||
onClose={() => setWizard(null)}
|
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) && (
|
{advancedSettings && (
|
||||||
<Box display="flex" alignItems="center" flexDirection="column" mt={15}>
|
<AdvancedTriggers
|
||||||
<H2>
|
error={error}
|
||||||
It seems there are no active compliance triggers on your network
|
save={saveConfig}
|
||||||
</H2>
|
data={data}></AdvancedTriggers>
|
||||||
<Button onClick={() => setWizard(true)}>Add first trigger</Button>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
requirements,
|
||||||
sortBy,
|
sortBy,
|
||||||
fromServer,
|
fromServer,
|
||||||
toServer
|
toServer,
|
||||||
|
getView,
|
||||||
|
requirementOptions
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ const namespaces = {
|
||||||
COMMISSIONS: 'commissions',
|
COMMISSIONS: 'commissions',
|
||||||
RECEIPT: 'receipt',
|
RECEIPT: 'receipt',
|
||||||
COIN_ATM_RADAR: 'coinAtmRadar',
|
COIN_ATM_RADAR: 'coinAtmRadar',
|
||||||
TERMS_CONDITIONS: 'termsConditions'
|
TERMS_CONDITIONS: 'termsConditions',
|
||||||
|
TRIGGERS: 'triggersConfig'
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapKeys = R.curry((fn, obj) =>
|
const mapKeys = R.curry((fn, obj) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue