{filterSuggestions(
suggestions,
inputValue2,
- value ? R.of(value) : []
+ value ? R.of(value) : [],
+ code,
+ display
).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion }),
highlightedIndex,
- selectedItem: selectedItem2
+ selectedItem: selectedItem2,
+ code,
+ display
})
)}
diff --git a/new-lamassu-admin/src/components/inputs/autocomplete/commons.js b/new-lamassu-admin/src/components/inputs/autocomplete/commons.js
index dae6045a..03e40d2c 100644
--- a/new-lamassu-admin/src/components/inputs/autocomplete/commons.js
+++ b/new-lamassu-admin/src/components/inputs/autocomplete/commons.js
@@ -1,32 +1,31 @@
import MenuItem from '@material-ui/core/MenuItem'
-import TextField from '@material-ui/core/TextField'
import Fuse from 'fuse.js'
-import * as R from 'ramda'
import React from 'react'
import slugify from 'slugify'
+import { withStyles } from '@material-ui/core/styles'
import {
fontColor,
inputFontSize,
- inputFontWeight
+ inputFontWeight,
+ zircon
} from 'src/styling/variables'
import S from 'src/utils/sanctuary'
-function renderInput(inputProps) {
- const { onBlur, success, InputProps, classes, ref, ...other } = inputProps
+import { TextInput } from '../base'
+
+function renderInput({ InputProps, error, name, success, ...props }) {
+ const { onChange, onBlur, value } = InputProps
return (
-
)
}
@@ -36,29 +35,44 @@ function renderSuggestion({
index,
itemProps,
highlightedIndex,
- selectedItem
+ selectedItem,
+ code,
+ display
}) {
const isHighlighted = highlightedIndex === index
- const item = R.o(R.defaultTo(''), R.path(['display']))(selectedItem)
- const isSelected = R.indexOf(suggestion.display)(item) > -1
+ const StyledMenuItem = withStyles(theme => ({
+ root: {
+ fontSize: 14,
+ fontWeight: 400,
+ color: fontColor
+ },
+ selected: {
+ '&.Mui-selected, &.Mui-selected:hover': {
+ fontWeight: 500,
+ backgroundColor: zircon
+ }
+ }
+ }))(MenuItem)
return (
-
+ component="div">
+ {suggestion[display]}
+
)
}
-function filterSuggestions(suggestions = [], value = '', currentValues = []) {
+function filterSuggestions(
+ suggestions = [],
+ value = '',
+ currentValues = [],
+ code,
+ display
+) {
const options = {
shouldSort: true,
threshold: 0.2,
@@ -66,14 +80,15 @@ function filterSuggestions(suggestions = [], value = '', currentValues = []) {
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
- keys: ['code', 'display']
+ code,
+ display
}
const fuse = new Fuse(suggestions, options)
const result = value ? fuse.search(slugify(value, ' ')) : suggestions
- const currentCodes = S.map(S.prop('code'))(currentValues)
- const filtered = S.filter(it => !S.elem(it.code)(currentCodes))(result)
+ const currentCodes = S.map(S.prop(code))(currentValues)
+ const filtered = S.filter(it => !S.elem(it[code])(currentCodes))(result)
const amountToTake = S.min(filtered.length)(5)
diff --git a/new-lamassu-admin/src/components/inputs/base/Switch.js b/new-lamassu-admin/src/components/inputs/base/Switch.js
index 7e601547..e7a19ae9 100644
--- a/new-lamassu-admin/src/components/inputs/base/Switch.js
+++ b/new-lamassu-admin/src/components/inputs/base/Switch.js
@@ -26,7 +26,11 @@ const useStyles = makeStyles(theme => ({
}
},
'&$checked': {
+ transform: 'translateX(58%)',
color: theme.palette.common.white,
+ '&$disabled': {
+ color: disabledColor2
+ },
'& + $track': {
backgroundColor: secondaryColor,
opacity: 1,
diff --git a/new-lamassu-admin/src/components/inputs/base/TextInput.js b/new-lamassu-admin/src/components/inputs/base/TextInput.js
index 835b5730..6f835064 100644
--- a/new-lamassu-admin/src/components/inputs/base/TextInput.js
+++ b/new-lamassu-admin/src/components/inputs/base/TextInput.js
@@ -1,6 +1,5 @@
import React, { memo } from 'react'
import classnames from 'classnames'
-import InputAdornment from '@material-ui/core/InputAdornment'
import TextField from '@material-ui/core/TextField'
import { makeStyles } from '@material-ui/core/styles'
@@ -10,27 +9,49 @@ import {
secondaryColor,
inputFontSize,
inputFontSizeLg,
- inputFontWeight
+ inputFontWeight,
+ inputFontWeightLg
} from 'src/styling/variables'
+import { TL2, Label2, Info1, Info2 } from 'src/components/typography'
const useStyles = makeStyles({
+ wrapper: {
+ display: 'inline-block',
+ maxWidth: '100%',
+ '& > span': {
+ display: 'flex',
+ alignItems: 'baseline',
+ '& > p:first-child': {
+ margin: [[0, 4, 5, 0]]
+ },
+ '&> p:last-child': {
+ margin: [[0, 0, 0, 3]]
+ }
+ }
+ },
inputRoot: {
fontSize: inputFontSize,
color: fontColor,
fontWeight: inputFontWeight,
- paddingLeft: 4
+ paddingLeft: 4,
+ '& > .MuiInputBase-input': {
+ width: 282
+ }
},
inputRootLg: {
fontSize: inputFontSizeLg,
color: fontColor,
- fontWeight: inputFontWeight
+ fontWeight: inputFontWeightLg,
+ '& > .MuiInputBase-input': {
+ width: 96
+ }
},
labelRoot: {
color: fontColor,
paddingLeft: 4
},
root: {
- '& .MuiInput-underline:before': {
+ '& > .MuiInput-underline:before': {
borderBottom: [[2, 'solid', fontColor]]
},
'& .Mui-focused': {
@@ -40,9 +61,6 @@ const useStyles = makeStyles({
paddingTop: 4,
paddingBottom: 3
},
- '& .MuiInputBase-input': {
- width: 282
- },
'& .MuiInputBase-inputMultiline': {
width: 500,
paddingRight: 20
@@ -66,6 +84,23 @@ const useStyles = makeStyles({
}
})
+const TextInputDisplay = memo(({ display, suffix, large }) => {
+ const classes = useStyles()
+
+ return (
+
+
+ {large && !suffix && {display}}
+ {!large && !suffix && {display}}
+ {large && suffix && {display}}
+ {!large && suffix && {display}}
+ {suffix && large && {suffix}}
+ {suffix && !large && {suffix}}
+
+
+ )
+})
+
const TextInput = memo(
({
name,
@@ -76,6 +111,7 @@ const TextInput = memo(
suffix,
large,
className,
+ InputProps,
...props
}) => {
const classes = useStyles()
@@ -87,30 +123,33 @@ const TextInput = memo(
}
return (
-
- {suffix}
-
- ) : null
- }}
- InputLabelProps={{ className: classes.labelRoot }}
- {...props}
- />
+
+
+
+ {suffix && large && (
+ <>
+ {suffix}
+ >
+ )}
+ {suffix && !large && {suffix}}
+
+
)
}
)
-export default TextInput
+export { TextInput, TextInputDisplay }
diff --git a/new-lamassu-admin/src/components/inputs/base/index.js b/new-lamassu-admin/src/components/inputs/base/index.js
index f52fc5e8..b56e6329 100644
--- a/new-lamassu-admin/src/components/inputs/base/index.js
+++ b/new-lamassu-admin/src/components/inputs/base/index.js
@@ -1,5 +1,5 @@
import Checkbox from './Checkbox'
-import TextInput from './TextInput'
+import { TextInput } from './TextInput'
import Switch from './Switch'
import RadioGroup from './RadioGroup'
diff --git a/new-lamassu-admin/src/components/inputs/index.js b/new-lamassu-admin/src/components/inputs/index.js
index 7ffe2a38..1650643e 100644
--- a/new-lamassu-admin/src/components/inputs/index.js
+++ b/new-lamassu-admin/src/components/inputs/index.js
@@ -5,7 +5,7 @@ import Radio from './base/Radio'
import RadioGroup from './base/RadioGroup'
import Select from './base/Select'
import Switch from './base/Switch'
-import TextInput from './base/TextInput'
+import { TextInput } from './base/TextInput'
export {
Autocomplete,
diff --git a/new-lamassu-admin/src/components/typography/index.js b/new-lamassu-admin/src/components/typography/index.js
index 004d3c3a..8736e878 100644
--- a/new-lamassu-admin/src/components/typography/index.js
+++ b/new-lamassu-admin/src/components/typography/index.js
@@ -36,7 +36,7 @@ function H3({ children, className, ...props }) {
function H4({ children, className, ...props }) {
const classes = useStyles()
return (
-
+
{children}
)
diff --git a/new-lamassu-admin/src/pages/Notifications/CryptoBalanceAlerts.js b/new-lamassu-admin/src/pages/Notifications/CryptoBalanceAlerts.js
new file mode 100644
index 00000000..f6864759
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Notifications/CryptoBalanceAlerts.js
@@ -0,0 +1,254 @@
+import React, { useState } from 'react'
+import * as R from 'ramda'
+import { gql } from 'apollo-boost'
+import classnames from 'classnames'
+import * as Yup from 'yup'
+import { makeStyles } from '@material-ui/core'
+import { useQuery } from '@apollo/react-hooks'
+
+import { Info2 } from 'src/components/typography'
+import commonStyles from 'src/pages/common.styles'
+import { Table as EditableTable } from 'src/components/editableTable'
+import Link from 'src/components/buttons/Link.js'
+import { Autocomplete } from 'src/components/inputs/index.js'
+import { AddButton } from 'src/components/buttons/index.js'
+import TextInputFormik from 'src/components/inputs/formik/TextInput.js'
+
+import {
+ isDisabled,
+ LOW_BALANCE_KEY,
+ HIGH_BALANCE_KEY,
+ OVERRIDES_KEY,
+ ADD_OVERRIDE_CBA_KEY
+} from './aux.js'
+import { BigNumericInput } from './Inputs'
+import { localStyles, cryptoBalanceAlertsStyles } from './Notifications.styles'
+
+const CRYPTOCURRENCY_KEY = 'cryptocurrency'
+
+const styles = R.mergeAll([
+ commonStyles,
+ localStyles,
+ cryptoBalanceAlertsStyles
+])
+
+const GET_CRYPTOCURRENCIES = gql`
+ {
+ cryptoCurrencies {
+ code
+ display
+ }
+ }
+`
+
+const useStyles = makeStyles(styles)
+
+const CryptoBalanceAlerts = ({
+ values: setupValues,
+ save,
+ editingState,
+ handleEditingClick,
+ setError
+}) => {
+ const [cryptoCurrencies, setCryptoCurrencies] = useState(null)
+
+ useQuery(GET_CRYPTOCURRENCIES, {
+ onCompleted: data => {
+ setCryptoCurrencies(data.cryptoCurrencies)
+ },
+ onError: error => console.error(error)
+ })
+ const classes = useStyles()
+
+ const editingLowBalance = editingState[LOW_BALANCE_KEY]
+ const editingHighBalance = editingState[HIGH_BALANCE_KEY]
+ const addingOverride = editingState[ADD_OVERRIDE_CBA_KEY]
+
+ const overrideOpsDisabled = isDisabled(editingState, ADD_OVERRIDE_CBA_KEY)
+
+ const handleEdit = R.curry(handleEditingClick)
+
+ const handleSubmit = it => save(it)
+
+ const handleSubmitOverrides = it => {
+ const newOverrides = {
+ [OVERRIDES_KEY]: R.prepend(it, setupValues[OVERRIDES_KEY])
+ }
+ save(newOverrides)
+ }
+
+ const handleResetForm = () => {
+ handleEdit(ADD_OVERRIDE_CBA_KEY)(false)
+ setError(null)
+ }
+
+ const deleteOverride = it => {
+ const cryptocurrency = it[CRYPTOCURRENCY_KEY]
+
+ const idx = R.findIndex(
+ R.propEq([CRYPTOCURRENCY_KEY], cryptocurrency),
+ setupValues[OVERRIDES_KEY]
+ )
+ const newOverrides = R.remove(idx, 1, setupValues[OVERRIDES_KEY])
+
+ save({ [OVERRIDES_KEY]: newOverrides })
+ }
+
+ const defaultsFields = {
+ [LOW_BALANCE_KEY]: {
+ name: LOW_BALANCE_KEY,
+ label: 'Alert me under',
+ value: setupValues[LOW_BALANCE_KEY]
+ },
+ [HIGH_BALANCE_KEY]: {
+ name: HIGH_BALANCE_KEY,
+ label: 'Alert me over',
+ value: setupValues[HIGH_BALANCE_KEY]
+ }
+ }
+
+ const getSuggestions = () => {
+ const overridenCryptos = R.map(
+ override => override[CRYPTOCURRENCY_KEY],
+ setupValues[OVERRIDES_KEY]
+ )
+ return R.without(overridenCryptos, cryptoCurrencies ?? [])
+ }
+
+ const { [OVERRIDES_KEY]: overrides } = setupValues
+
+ const initialValues = {
+ [CRYPTOCURRENCY_KEY]: '',
+ [LOW_BALANCE_KEY]: '',
+ [HIGH_BALANCE_KEY]: ''
+ }
+
+ const validationSchema = Yup.object().shape({
+ [CRYPTOCURRENCY_KEY]: Yup.string().required(),
+ [LOW_BALANCE_KEY]: Yup.number()
+ .integer()
+ .min(0)
+ .max(99999999)
+ .required(),
+ [HIGH_BALANCE_KEY]: Yup.number()
+ .integer()
+ .min(0)
+ .max(99999999)
+ .required()
+ })
+
+ const elements = [
+ {
+ name: CRYPTOCURRENCY_KEY,
+ display: 'Cryptocurrency',
+ size: 166,
+ textAlign: 'left',
+ view: R.path(['display']),
+ type: 'text',
+ input: Autocomplete,
+ inputProps: {
+ suggestions: getSuggestions(),
+ onFocus: () => setError(null)
+ }
+ },
+ {
+ name: LOW_BALANCE_KEY,
+ display: 'Low Balance',
+ size: 140,
+ textAlign: 'right',
+ view: it => it,
+ type: 'text',
+ input: TextInputFormik,
+ inputProps: {
+ suffix: 'EUR', // TODO: Current currency?
+ className: classes.textInput,
+ onFocus: () => setError(null)
+ }
+ },
+ {
+ name: HIGH_BALANCE_KEY,
+ display: 'High Balance',
+ size: 140,
+ textAlign: 'right',
+ view: it => it,
+ type: 'text',
+ input: TextInputFormik,
+ inputProps: {
+ suffix: 'EUR', // TODO: Current currency?
+ className: classes.textInput,
+ onFocus: () => setError(null)
+ }
+ },
+ {
+ name: 'delete',
+ size: 91
+ }
+ ]
+
+ if (!cryptoCurrencies) return null
+
+ return (
+ <>
+
+
+
+ Overrides
+ {!addingOverride && !overrideOpsDisabled && overrides.length > 0 && (
+ handleEdit(ADD_OVERRIDE_CBA_KEY)(true)}>
+ Add override
+
+ )}
+
+ {!addingOverride && !overrideOpsDisabled && overrides.length === 0 && (
+
handleEdit(ADD_OVERRIDE_CBA_KEY)(true)}>
+ Add overrides
+
+ )}
+ {(addingOverride || overrides.length > 0) && (
+
false,
+ R.range(0, setupValues[OVERRIDES_KEY].length)
+ )}
+ save={handleSubmitOverrides}
+ reset={handleResetForm}
+ action={deleteOverride}
+ initialValues={initialValues}
+ validationSchema={validationSchema}
+ data={setupValues[OVERRIDES_KEY]}
+ elements={elements}
+ />
+ )}
+
+ >
+ )
+}
+
+export default CryptoBalanceAlerts
diff --git a/new-lamassu-admin/src/pages/Notifications/FiatBalanceAlerts.js b/new-lamassu-admin/src/pages/Notifications/FiatBalanceAlerts.js
new file mode 100644
index 00000000..a25aee90
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Notifications/FiatBalanceAlerts.js
@@ -0,0 +1,597 @@
+import React, { useState } from 'react'
+import * as R from 'ramda'
+import classnames from 'classnames'
+import { gql } from 'apollo-boost'
+import * as Yup from 'yup'
+import { makeStyles } from '@material-ui/core'
+import { useQuery } from '@apollo/react-hooks'
+
+import { Info2 } from 'src/components/typography'
+import commonStyles from 'src/pages/common.styles'
+import { Table as EditableTable } from 'src/components/editableTable'
+import { Link, AddButton } from 'src/components/buttons'
+import { Autocomplete } from 'src/components/inputs'
+import TextInputFormik from 'src/components/inputs/formik/TextInput.js'
+
+import { BigPercentageAndNumericInput, MultiplePercentageInput } from './Inputs'
+import { localStyles, fiatBalanceAlertsStyles } from './Notifications.styles'
+import {
+ CASH_IN_FULL_KEY,
+ isDisabled,
+ CASH_OUT_EMPTY_KEY,
+ CASSETTE_1_KEY,
+ CASSETTE_2_KEY,
+ PERCENTAGE_KEY,
+ NUMERARY_KEY,
+ OVERRIDES_KEY,
+ ADD_OVERRIDE_FBA_KEY,
+ MACHINE_KEY
+} from './aux'
+
+const styles = R.mergeAll([commonStyles, localStyles, fiatBalanceAlertsStyles])
+
+const useStyles = makeStyles(styles)
+
+const GET_MACHINES = gql`
+ {
+ machines {
+ name
+ deviceId
+ }
+ }
+`
+
+// const OverridesRow = ({
+// machine,
+// handleSubmitOverrides,
+// handleEdit,
+// setError,
+// sizes,
+// editing,
+// fields,
+// disabled,
+// getSuggestions,
+// ...props
+// }) => {
+// const classes = useStyles()
+
+// const baseInitialValues = {
+// [fields[PERCENTAGE_KEY].name]: fields[PERCENTAGE_KEY].value ?? '',
+// [fields[NUMERARY_KEY].name]: fields[NUMERARY_KEY].value ?? '',
+// [fields[CASSETTE_1_KEY].name]: fields[CASSETTE_1_KEY].value ?? '',
+// [fields[CASSETTE_2_KEY].name]: fields[CASSETTE_2_KEY].value ?? ''
+// }
+
+// const initialValues = machine
+// ? baseInitialValues
+// : R.assoc(fields[MACHINE_KEY].name, '', baseInitialValues)
+
+// const baseValidationSchemaShape = {
+// [fields[PERCENTAGE_KEY].name]: Yup.number()
+// .integer()
+// .min(0)
+// .max(100)
+// .required(),
+// [fields[NUMERARY_KEY].name]: Yup.number()
+// .integer()
+// .min(0)
+// .max(99999999)
+// .required(),
+// [fields[CASSETTE_1_KEY].name]: Yup.number()
+// .integer()
+// .min(0)
+// .max(100)
+// .required(),
+// [fields[CASSETTE_2_KEY].name]: Yup.number()
+// .integer()
+// .min(0)
+// .max(100)
+// .required()
+// }
+
+// const validationSchemaShape = machine
+// ? baseValidationSchemaShape
+// : R.assoc(
+// fields[MACHINE_KEY].name,
+// Yup.string().required(),
+// baseValidationSchemaShape
+// )
+
+// return (
+// {
+// const machineName = machine
+// ? machine.name
+// : values[fields[MACHINE_KEY].name].name
+// handleSubmitOverrides(machineName)(values)
+// }}
+// onReset={(values, bag) => {
+// handleEdit(machine?.name ?? ADD_OVERRIDE_FBA_KEY)(false)
+// setError(null)
+// }}>
+//
+//
+// )
+// }
+
+const FiatBalanceAlerts = ({
+ values: setupValues,
+ save,
+ editingState,
+ handleEditingClick,
+ setError
+}) => {
+ const [machines, setMachines] = useState(null)
+ useQuery(GET_MACHINES, {
+ onCompleted: data => {
+ setMachines(data.machines)
+ },
+ onError: error => console.error(error)
+ })
+
+ const classes = useStyles()
+
+ const getValue = R.path(R.__, setupValues)
+
+ const handleEdit = R.curry(handleEditingClick)
+
+ const handleSubmit = R.curry((key, it) => {
+ const setup = setupValues[key]
+ const pairs = R.mapObjIndexed((num, k, obj) => {
+ return [R.split('-', k)[1], num]
+ }, it)
+ const rightKeys = R.fromPairs(R.values(pairs))
+ const newItem = { [key]: R.merge(setup, rightKeys) }
+ save(newItem)
+ })
+
+ const handleSubmitOverrides = R.curry((key, it) => {
+ const setup = setupValues[OVERRIDES_KEY]
+ const pathMatches = R.pathEq(['machine', 'name'], key)
+
+ const pairs = R.values(
+ R.mapObjIndexed((num, k, obj) => {
+ const split = R.split('-', k)
+ if (split.length < 3) return { [split[1]]: num }
+ return { [split[1]]: { [split[2]]: num } }
+ }, it)
+ )
+
+ const old = R.find(pathMatches, setup)
+ if (!old) {
+ const newOverride = R.reduce(R.mergeDeepRight, {}, pairs)
+ const newOverrides = {
+ [OVERRIDES_KEY]: R.prepend(newOverride, setup)
+ }
+ save(newOverrides)
+ return
+ }
+
+ const machineIdx = R.findIndex(pathMatches, setup)
+ const newOverride = R.mergeDeepRight(
+ old,
+ R.reduce(R.mergeDeepRight, {}, pairs)
+ )
+ const newOverrides = {
+ [OVERRIDES_KEY]: R.update(machineIdx, newOverride, setup)
+ }
+ save(newOverrides)
+ })
+
+ const handleResetForm = it => {
+ const machine = it?.machine
+ handleEdit(machine?.name ?? ADD_OVERRIDE_FBA_KEY)(false)
+ setError(null)
+ }
+
+ const getSuggestions = () => {
+ const overridenMachines = R.map(
+ override => override.machine,
+ setupValues[OVERRIDES_KEY]
+ )
+ return R.without(overridenMachines, machines ?? [])
+ }
+
+ const cashInFields = {
+ percentage: {
+ name: CASH_IN_FULL_KEY + '-' + PERCENTAGE_KEY,
+ label: 'Alert me over',
+ value: getValue([CASH_IN_FULL_KEY, PERCENTAGE_KEY])
+ },
+ numeric: {
+ name: CASH_IN_FULL_KEY + '-' + NUMERARY_KEY,
+ label: 'Or',
+ value: getValue([CASH_IN_FULL_KEY, NUMERARY_KEY])
+ }
+ }
+
+ const cashOutFields = [
+ {
+ title: 'Cassette 1 (Top)',
+ name: CASH_OUT_EMPTY_KEY + '-' + CASSETTE_1_KEY,
+ label: 'Alert me at',
+ value: getValue([CASH_OUT_EMPTY_KEY, CASSETTE_1_KEY])
+ },
+ {
+ title: 'Cassette 2',
+ name: CASH_OUT_EMPTY_KEY + '-' + CASSETTE_2_KEY,
+ label: 'Alert me at',
+ value: getValue([CASH_OUT_EMPTY_KEY, CASSETTE_2_KEY])
+ }
+ ]
+
+ const { overrides } = setupValues
+
+ const initialValues = {
+ [MACHINE_KEY]: '',
+ [PERCENTAGE_KEY]: '',
+ [NUMERARY_KEY]: '',
+ [CASSETTE_1_KEY]: '',
+ [CASSETTE_2_KEY]: ''
+ }
+
+ const validationSchema = Yup.object().shape({
+ [MACHINE_KEY]: Yup.string().required(),
+ [PERCENTAGE_KEY]: Yup.number()
+ .integer()
+ .min(0)
+ .max(100)
+ .required(),
+ [NUMERARY_KEY]: Yup.number()
+ .integer()
+ .min(0)
+ .max(99999999)
+ .required(),
+ [CASSETTE_1_KEY]: Yup.number()
+ .integer()
+ .min(0)
+ .max(100)
+ .required(),
+ [CASSETTE_2_KEY]: Yup.number()
+ .integer()
+ .min(0)
+ .max(100)
+ .required()
+ })
+
+ const elements = [
+ {
+ name: MACHINE_KEY,
+ display: 'Machine',
+ size: 238,
+ textAlign: 'left',
+ view: R.path(['display']),
+ type: 'text',
+ input: Autocomplete,
+ inputProps: {
+ suggestions: getSuggestions(),
+ onFocus: () => setError(null)
+ }
+ },
+ [
+ { display: 'Cash-in (Cassette Full)' },
+ {
+ name: PERCENTAGE_KEY,
+ display: 'Percentage',
+ size: 128,
+ textAlign: 'right',
+ view: it => it,
+ type: 'text',
+ input: TextInputFormik,
+ inputProps: {
+ suffix: '%',
+ className: classes.textInput,
+ onFocus: () => setError(null)
+ }
+ },
+ {
+ name: NUMERARY_KEY,
+ display: 'Amount',
+ size: 128,
+ textAlign: 'right',
+ view: it => it,
+ type: 'text',
+ input: TextInputFormik,
+ inputProps: {
+ suffix: 'EUR', // TODO: Current currency?
+ className: classes.textInput,
+ onFocus: () => setError(null)
+ }
+ }
+ ],
+ [
+ { display: 'Cash-out (Cassette Empty)' },
+ {
+ name: CASSETTE_1_KEY,
+ display: 'Cash-out 1',
+ size: 128,
+ textAlign: 'right',
+ view: it => it,
+ type: 'text',
+ input: TextInputFormik,
+ inputProps: {
+ suffix: '%',
+ className: classes.textInput,
+ onFocus: () => setError(null)
+ }
+ },
+ {
+ name: CASSETTE_2_KEY,
+ display: 'Cash-out 2',
+ size: 128,
+ textAlign: 'right',
+ view: it => it,
+ type: 'text',
+ input: TextInputFormik,
+ inputProps: {
+ suffix: '%',
+ className: classes.textInput,
+ onFocus: () => setError(null)
+ }
+ }
+ ],
+ {
+ name: 'edit',
+ size: 98
+ }
+ ]
+
+ const cashInEditing = editingState[CASH_IN_FULL_KEY]
+ const cashOutEditing = editingState[CASH_OUT_EMPTY_KEY]
+ const overrideOpsDisabled = isDisabled(editingState, ADD_OVERRIDE_FBA_KEY)
+ const addingOverride = editingState[ADD_OVERRIDE_FBA_KEY]
+
+ if (!machines) return null
+
+ return (
+
+
+
+
+ Overrides
+ {!addingOverride && !overrideOpsDisabled && overrides.length > 0 && (
+ handleEdit(ADD_OVERRIDE_FBA_KEY)(true)}>
+ Add override
+
+ )}
+
+ {!addingOverride && !overrideOpsDisabled && overrides.length === 0 && (
+
handleEdit(ADD_OVERRIDE_FBA_KEY)(true)}>
+ Add overrides
+
+ )}
+ {(addingOverride || overrides.length > 0) && (
+
false,
+ R.range(0, setupValues[OVERRIDES_KEY].length)
+ )}
+ save={handleSubmitOverrides}
+ reset={() => handleResetForm}
+ action={it => handleEdit(it.machine.name)(true)}
+ initialValues={initialValues}
+ validationSchema={validationSchema}
+ // data={setupValues[OVERRIDES_KEY]}
+ data={[]}
+ elements={elements}
+ double
+ />
+ //
+ //
+ // | Machine |
+ //
+ //
+ // Percentage
+ // |
+ //
+ // Amount
+ // |
+ //
+ //
+ //
+ // Cash-out 1
+ // |
+ //
+ // Cash-out 2
+ // |
+ //
+ //
+ // Edit
+ // |
+ //
+ //
+ // {addingOverride && (
+ //
+ // )}
+ // {overrides.map((override, idx) => {
+ // const machine = override[MACHINE_KEY]
+
+ // const fields = {
+ // [PERCENTAGE_KEY]: {
+ // name: `${machine.name}-${CASH_IN_FULL_KEY}-${PERCENTAGE_KEY}`,
+ // value: override[CASH_IN_FULL_KEY][PERCENTAGE_KEY]
+ // },
+ // [NUMERARY_KEY]: {
+ // name: `${machine.name}-${CASH_IN_FULL_KEY}-${NUMERARY_KEY}`,
+ // value: override[CASH_IN_FULL_KEY][NUMERARY_KEY]
+ // },
+ // [CASSETTE_1_KEY]: {
+ // name: `${machine.name}-${CASH_OUT_EMPTY_KEY}-${CASSETTE_1_KEY}`,
+ // value: override[CASH_OUT_EMPTY_KEY][CASSETTE_1_KEY]
+ // },
+ // [CASSETTE_2_KEY]: {
+ // name: `${machine.name}-${CASH_OUT_EMPTY_KEY}-${CASSETTE_2_KEY}`,
+ // value: override[CASH_OUT_EMPTY_KEY][CASSETTE_2_KEY]
+ // }
+ // }
+
+ // const editing = editingState[machine.name]
+ // const disabled = isDisabled(editingState, machine.name)
+
+ // return (
+ //
+ // )
+ // })}
+ //
+ //
+ )}
+
+
+ )
+}
+
+export default FiatBalanceAlerts
diff --git a/new-lamassu-admin/src/pages/Notifications/Inputs.js b/new-lamassu-admin/src/pages/Notifications/Inputs.js
new file mode 100644
index 00000000..8bcc0b70
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Notifications/Inputs.js
@@ -0,0 +1,340 @@
+import React from 'react'
+import * as R from 'ramda'
+import classnames from 'classnames'
+import * as Yup from 'yup'
+import { Form, Formik, Field as FormikField } from 'formik'
+import { makeStyles } from '@material-ui/core'
+
+import {
+ H4,
+ Label1,
+ Info1,
+ TL2,
+ Info2,
+ Label2
+} from 'src/components/typography'
+import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
+import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg'
+import { Link } from 'src/components/buttons'
+import TextInputFormik from 'src/components/inputs/formik/TextInput'
+
+import {
+ localStyles,
+ inputSectionStyles,
+ percentageAndNumericInputStyles,
+ multiplePercentageInputStyles,
+ fieldStyles
+} from './Notifications.styles'
+
+const fieldUseStyles = makeStyles(R.mergeAll([fieldStyles, localStyles]))
+
+const Field = ({
+ editing,
+ field,
+ displayValue,
+ decoration,
+ className,
+ setError,
+ ...props
+}) => {
+ const classes = fieldUseStyles()
+
+ const classNames = {
+ [className]: true,
+ [classes.field]: true,
+ [classes.notEditing]: !editing,
+ [classes.percentageInput]: decoration === '%'
+ }
+
+ return (
+
+ {field.label &&
{field.label}}
+
+ {!editing && props.large && (
+ <>
+ {displayValue(field.value)}
+ >
+ )}
+ {!editing && !props.large && (
+ <>
+ {displayValue(field.value)}
+ >
+ )}
+ {editing && (
+ setError(null)}
+ {...props}
+ />
+ )}
+ {props.large && (
+ <>
+ {decoration}
+ >
+ )}
+ {!props.large && (
+ <>
+ {decoration}
+ >
+ )}
+
+
+ )
+}
+
+const useStyles = makeStyles(inputSectionStyles)
+
+const Header = ({ title, editing, disabled, setEditing }) => {
+ const classes = useStyles()
+
+ return (
+
+
{title}
+ {!editing && !disabled && (
+
+ )}
+ {disabled && (
+
+
+
+ )}
+ {editing && (
+
+
+ Save
+
+
+ Cancel
+
+
+ )}
+
+ )
+}
+
+const BigNumericInput = ({
+ title,
+ field,
+ editing,
+ disabled,
+ setEditing,
+ handleSubmit,
+ setError,
+ className
+}) => {
+ const classes = useStyles()
+
+ const { name, value } = field
+
+ return (
+
+
{
+ handleSubmit(values)
+ }}
+ onReset={(values, bag) => {
+ setEditing(false)
+ setError(null)
+ }}>
+
+
+
+ )
+}
+
+const percentageAndNumericInputUseStyles = makeStyles(
+ R.merge(inputSectionStyles, percentageAndNumericInputStyles)
+)
+
+const BigPercentageAndNumericInput = ({
+ title,
+ fields,
+ editing,
+ disabled,
+ setEditing,
+ handleSubmit,
+ setError,
+ className
+}) => {
+ const classes = percentageAndNumericInputUseStyles()
+
+ const { percentage, numeric } = fields
+ const { name: percentageName, value: percentageValue } = percentage
+ const { name: numericName, value: numericValue } = numeric
+
+ return (
+
+
{
+ handleSubmit(values)
+ }}
+ onReset={(values, bag) => {
+ setEditing(false)
+ setError(null)
+ }}>
+
+
+
+ )
+}
+
+const multiplePercentageInputUseStyles = makeStyles(
+ R.merge(inputSectionStyles, multiplePercentageInputStyles)
+)
+
+const MultiplePercentageInput = ({
+ title,
+ fields,
+ editing,
+ disabled,
+ setEditing,
+ handleSubmit,
+ setError,
+ className
+}) => {
+ const classes = multiplePercentageInputUseStyles()
+
+ const initialValues = R.fromPairs(R.map(f => [f.name, f.value], fields))
+ const validationSchemaShape = R.fromPairs(
+ R.map(
+ f => [
+ f.name,
+ Yup.number()
+ .integer()
+ .min(0)
+ .max(100)
+ .required()
+ ],
+ fields
+ )
+ )
+
+ return (
+
+
{
+ handleSubmit(values)
+ }}
+ onReset={(values, bag) => {
+ setEditing(false)
+ setError(null)
+ }}>
+
+
+
+ )
+}
+
+export {
+ Field,
+ BigNumericInput,
+ BigPercentageAndNumericInput,
+ MultiplePercentageInput
+}
diff --git a/new-lamassu-admin/src/pages/Notifications/Notifications.js b/new-lamassu-admin/src/pages/Notifications/Notifications.js
new file mode 100644
index 00000000..0c919469
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Notifications/Notifications.js
@@ -0,0 +1,232 @@
+import React, { useState } from 'react'
+import * as R from 'ramda'
+import { gql } from 'apollo-boost'
+import { makeStyles } from '@material-ui/core'
+import { useQuery, useMutation } from '@apollo/react-hooks'
+
+import { TL1 } from 'src/components/typography'
+import Title from 'src/components/Title'
+import ErrorMessage from 'src/components/ErrorMessage'
+import commonStyles from 'src/pages/common.styles'
+
+import { localStyles } from './Notifications.styles'
+import Setup from './Setup'
+import TransactionAlerts from './TransactionAlerts'
+import {
+ SETUP_KEY,
+ TRANSACTION_ALERTS_KEY,
+ HIGH_VALUE_TRANSACTION_KEY,
+ CASH_IN_FULL_KEY,
+ FIAT_BALANCE_ALERTS_KEY,
+ CASH_OUT_EMPTY_KEY,
+ CASSETTE_1_KEY,
+ CASSETTE_2_KEY,
+ OVERRIDES_KEY,
+ PERCENTAGE_KEY,
+ NUMERARY_KEY,
+ CRYPTO_BALANCE_ALERTS_KEY,
+ LOW_BALANCE_KEY,
+ HIGH_BALANCE_KEY,
+ ADD_OVERRIDE_FBA_KEY,
+ ADD_OVERRIDE_CBA_KEY,
+ EMAIL_KEY,
+ BALANCE_KEY,
+ TRANSACTIONS_KEY,
+ COMPLIANCE_KEY,
+ SECURITY_KEY,
+ ERRORS_KEY,
+ ACTIVE_KEY,
+ SMS_KEY
+} from './aux.js'
+import FiatBalanceAlerts from './FiatBalanceAlerts'
+import CryptoBalanceAlerts from './CryptoBalanceAlerts'
+
+const fiatBalanceAlertsInitialValues = {
+ [CASH_IN_FULL_KEY]: {
+ [PERCENTAGE_KEY]: '',
+ [NUMERARY_KEY]: ''
+ },
+ [CASH_OUT_EMPTY_KEY]: {
+ [CASSETTE_1_KEY]: '',
+ [CASSETTE_2_KEY]: ''
+ }
+}
+
+const initialValues = {
+ [SETUP_KEY]: {
+ [EMAIL_KEY]: {
+ [BALANCE_KEY]: false,
+ [TRANSACTIONS_KEY]: false,
+ [COMPLIANCE_KEY]: false,
+ [SECURITY_KEY]: false,
+ [ERRORS_KEY]: false,
+ [ACTIVE_KEY]: false
+ },
+ [SMS_KEY]: {
+ [BALANCE_KEY]: false,
+ [TRANSACTIONS_KEY]: false,
+ [COMPLIANCE_KEY]: false,
+ [SECURITY_KEY]: false,
+ [ERRORS_KEY]: false,
+ [ACTIVE_KEY]: false
+ }
+ },
+ [TRANSACTION_ALERTS_KEY]: {
+ [HIGH_VALUE_TRANSACTION_KEY]: ''
+ },
+ [FIAT_BALANCE_ALERTS_KEY]: {
+ ...fiatBalanceAlertsInitialValues,
+ [OVERRIDES_KEY]: []
+ },
+ [CRYPTO_BALANCE_ALERTS_KEY]: {
+ [LOW_BALANCE_KEY]: '',
+ [HIGH_BALANCE_KEY]: '',
+ [OVERRIDES_KEY]: []
+ }
+}
+
+const initialEditingState = {
+ [HIGH_VALUE_TRANSACTION_KEY]: false,
+ [CASH_IN_FULL_KEY]: false,
+ [CASH_OUT_EMPTY_KEY]: false,
+ [LOW_BALANCE_KEY]: false,
+ [HIGH_BALANCE_KEY]: false,
+ [ADD_OVERRIDE_FBA_KEY]: false,
+ [ADD_OVERRIDE_CBA_KEY]: false
+}
+
+const GET_INFO = gql`
+ {
+ config
+ }
+`
+
+const SAVE_CONFIG = gql`
+ mutation Save($config: JSONObject) {
+ saveConfig(config: $config)
+ }
+`
+
+const styles = R.merge(commonStyles, localStyles)
+
+const useStyles = makeStyles(styles)
+
+const SectionHeader = ({ error, children }) => {
+ const classes = useStyles()
+
+ return (
+
+ {children}
+ {error && Failed to save changes}
+
+ )
+}
+
+const Notifications = () => {
+ const [state, setState] = useState(null)
+ const [editingState, setEditingState] = useState(initialEditingState)
+ const [error, setError] = useState(null)
+ const [tryingToSave, setTryingToSave] = useState(null)
+ const [saveConfig] = useMutation(SAVE_CONFIG, {
+ onCompleted: data => {
+ const { notifications } = data.saveConfig
+ setState(notifications)
+ setEditingState(R.map(x => false, editingState))
+ setTryingToSave(null)
+ setError(null)
+ },
+ onError: e => {
+ setError({ section: tryingToSave, error: e })
+ }
+ })
+ const classes = useStyles()
+
+ useQuery(GET_INFO, {
+ onCompleted: data => {
+ const { notifications } = data.config
+ if (notifications) {
+ const { [OVERRIDES_KEY]: machines } = notifications[
+ FIAT_BALANCE_ALERTS_KEY
+ ]
+ const editingFiatBalanceAlertsOverrides = R.fromPairs(
+ machines.map(machine => [machine.name, false])
+ )
+ setEditingState({
+ ...editingState,
+ ...editingFiatBalanceAlertsOverrides
+ })
+ }
+ setState(notifications ?? initialValues)
+ },
+ fetchPolicy: 'network-only'
+ })
+
+ const save = it => {
+ return saveConfig({ variables: { config: { notifications: it } } })
+ }
+
+ const handleEditingClick = (key, state) => {
+ setEditingState(R.merge(editingState, { [key]: state }))
+ }
+
+ const curriedSave = R.curry((key, values) => {
+ setTryingToSave(key)
+ save(R.mergeDeepRight(state)({ [key]: values }))
+ })
+
+ if (!state) return null
+
+ return (
+ <>
+
+
+
+ Setup
+
+
+
+
+
+ Transaction alerts
+
+
+
+
+
+ Fiat balance alerts
+
+
+
+
+
+ Crypto balance alerts
+
+
+
+ >
+ )
+}
+
+export default Notifications
diff --git a/new-lamassu-admin/src/pages/Notifications/Notifications.styles.js b/new-lamassu-admin/src/pages/Notifications/Notifications.styles.js
new file mode 100644
index 00000000..3f021375
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Notifications/Notifications.styles.js
@@ -0,0 +1,252 @@
+import { offColor, primaryColor } from 'src/styling/variables'
+import theme from 'src/styling/theme'
+
+const localStyles = {
+ section: {
+ marginBottom: 72,
+ '&:last-child': {
+ marginBottom: 150
+ }
+ },
+ sectionHeader: {
+ display: 'flex',
+ alignItems: 'center',
+ '& > :first-child': {
+ marginRight: 20
+ }
+ },
+ sectionTitle: {
+ color: offColor,
+ margin: [[16, 0, 16, 0]]
+ },
+ button: {
+ border: 'none',
+ backgroundColor: 'transparent',
+ cursor: 'pointer',
+ height: '100%'
+ },
+ defaults: {
+ display: 'flex',
+ '& > div': {
+ display: 'flex',
+ alignItems: 'center'
+ },
+ '& > div:first-child': {
+ borderRight: [['solid', 1, primaryColor]]
+ },
+ '& > div:not(:first-child)': {
+ marginLeft: 56
+ }
+ },
+ overrides: {
+ display: 'inline-block'
+ },
+ overridesTitle: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'baseline',
+ '& > :first-child': {
+ color: offColor,
+ margin: [[0, 0, 24, 0]]
+ },
+ '& > button': {
+ height: '100%'
+ }
+ },
+ displayValue: {
+ display: 'flex',
+ alignItems: 'baseline',
+ '& > p:first-child': {
+ margin: [[0, 4, 5, 0]]
+ },
+ '&> p:last-child': {
+ margin: 0
+ }
+ },
+ edit: {
+ display: 'flex',
+ justifyContent: 'flex-end',
+ '& > :first-child': {
+ marginRight: 16
+ }
+ },
+ eRowField: {
+ display: 'inline-block',
+ height: '100%'
+ },
+ textInput: {
+ '& .MuiInputBase-input': {
+ width: 80
+ }
+ }
+}
+
+const inputSectionStyles = {
+ header: {
+ display: 'flex',
+ alignItems: 'center',
+ marginBottom: 24,
+ height: 26,
+ '& > :first-child': {
+ flexShrink: 2,
+ margin: 0,
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+ textOverflow: 'ellipsis'
+ },
+ '& button': {
+ border: 'none',
+ backgroundColor: 'transparent',
+ cursor: 'pointer'
+ }
+ },
+ editButton: {
+ marginLeft: 16
+ },
+ disabledButton: {
+ padding: [[0, theme.spacing(1)]],
+ lineHeight: 'normal',
+ marginLeft: 16
+ },
+ editingButtons: {
+ display: 'flex',
+ marginLeft: 16,
+ '& > :not(:last-child)': {
+ marginRight: 20
+ }
+ },
+ percentageDisplay: {
+ position: 'relative',
+ width: 76,
+ height: 118,
+ border: [['solid', 4, primaryColor]],
+ marginRight: 12,
+ '& > div': {
+ position: 'absolute',
+ bottom: 0,
+ left: 0,
+ width: '100%',
+ backgroundColor: primaryColor,
+ transition: [['height', '0.5s']],
+ transitionTimingFunction: 'ease-out'
+ }
+ },
+ inputColumn: {
+ '& > div:not(:last-child)': {
+ marginBottom: 4
+ }
+ }
+}
+
+const fiatBalanceAlertsStyles = {
+ cashInWrapper: {
+ width: 254
+ },
+ doubleLevelHead: {
+ '& > div > div': {
+ marginRight: 72
+ }
+ },
+ doubleLevelRow: {
+ '& > div': {
+ marginRight: 72
+ }
+ },
+ fbaDefaults: {
+ '& > div': {
+ height: 185
+ },
+ marginBottom: 69
+ }
+}
+
+const cryptoBalanceAlertsStyles = {
+ lowBalance: {
+ width: 254,
+ '& form': {
+ width: 217
+ }
+ },
+ cbaDefaults: {
+ '& > div': {
+ height: 135
+ },
+ marginBottom: 36
+ },
+ overridesTable: {
+ width: 648
+ }
+}
+
+const percentageAndNumericInputStyles = {
+ body: {
+ display: 'flex',
+ alignItems: 'center'
+ }
+}
+
+const multiplePercentageInputStyles = {
+ body: {
+ display: 'flex',
+ '& > div': {
+ display: 'flex'
+ },
+ '& > div:not(:last-child)': {
+ marginRight: 43
+ }
+ },
+ title: {
+ margin: [[2, 0, 12, 0]]
+ }
+}
+
+const fieldStyles = {
+ field: {
+ position: 'relative',
+ display: 'flex',
+ flexDirection: 'column',
+ height: 53,
+ padding: 0,
+ '& > div': {
+ display: 'flex',
+ alignItems: 'baseline',
+ '& > p:first-child': {
+ margin: [[0, 4, 5, 0]]
+ },
+ '&> p:last-child': {
+ margin: [[0, 0, 0, 3]]
+ }
+ },
+ '& .MuiInputBase-input': {
+ width: 80
+ }
+ },
+ label: {
+ margin: 0
+ },
+ notEditing: {
+ '& > div': {
+ margin: [[5, 0, 0, 0]],
+ '& > p:first-child': {
+ height: 16
+ }
+ }
+ },
+ percentageInput: {
+ '& > div': {
+ '& .MuiInputBase-input': {
+ width: 30
+ }
+ }
+ }
+}
+
+export {
+ localStyles,
+ inputSectionStyles,
+ fiatBalanceAlertsStyles,
+ cryptoBalanceAlertsStyles,
+ percentageAndNumericInputStyles,
+ multiplePercentageInputStyles,
+ fieldStyles
+}
diff --git a/new-lamassu-admin/src/pages/Notifications/Setup.js b/new-lamassu-admin/src/pages/Notifications/Setup.js
new file mode 100644
index 00000000..2c824940
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Notifications/Setup.js
@@ -0,0 +1,148 @@
+import React from 'react'
+import * as R from 'ramda'
+
+import { Switch } from 'src/components/inputs'
+import {
+ Table as FakeTable,
+ THead,
+ TBody,
+ Tr,
+ Td,
+ Th
+} from 'src/components/fake-table/Table'
+
+import {
+ BALANCE_KEY,
+ TRANSACTIONS_KEY,
+ COMPLIANCE_KEY,
+ SECURITY_KEY,
+ ERRORS_KEY,
+ ACTIVE_KEY,
+ CHANNEL_KEY,
+ EMAIL_KEY,
+ SMS_KEY
+} from './aux'
+
+const elements = [
+ {
+ header: 'Channel',
+ name: CHANNEL_KEY,
+ size: 129,
+ textAlign: 'left'
+ },
+ {
+ header: 'Balance',
+ name: BALANCE_KEY,
+ size: 152,
+ textAlign: 'center'
+ },
+ {
+ header: 'Transactions',
+ name: TRANSACTIONS_KEY,
+ size: 184,
+ textAlign: 'center'
+ },
+ {
+ header: 'Compliance',
+ name: COMPLIANCE_KEY,
+ size: 178,
+ textAlign: 'center'
+ },
+ {
+ header: 'Security',
+ name: SECURITY_KEY,
+ size: 152,
+ textAlign: 'center'
+ },
+ {
+ header: 'Errors',
+ name: ERRORS_KEY,
+ size: 142,
+ textAlign: 'center'
+ },
+ {
+ header: 'Active',
+ name: ACTIVE_KEY,
+ size: 263,
+ textAlign: 'center'
+ }
+]
+
+const Row = ({ channel, columns, values, save }) => {
+ const { active } = values
+
+ const findField = name => R.find(R.propEq('name', name))(columns)
+ const findSize = name => findField(name).size
+ const findAlign = name => findField(name).textAlign
+
+ const Cell = ({ name, disabled }) => {
+ const handleChange = name => event => {
+ save(R.mergeDeepRight(values, { [name]: event.target.checked }))
+ }
+
+ return (
+
+
+ |
+ )
+ }
+
+ return (
+
+ |
+ {channel}
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+
+ )
+}
+
+const Setup = ({ values: setupValues, save }) => {
+ const saveSetup = R.curry((key, values) =>
+ save(R.mergeDeepRight(setupValues, { [key]: values }))
+ )
+
+ return (
+
+
+
+ {elements.map(({ size, className, textAlign, header }, idx) => (
+ |
+ {header}
+ |
+ ))}
+
+
+
+
+
+
+
+ )
+}
+
+export default Setup
diff --git a/new-lamassu-admin/src/pages/Notifications/TransactionAlerts.js b/new-lamassu-admin/src/pages/Notifications/TransactionAlerts.js
new file mode 100644
index 00000000..ded538a9
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Notifications/TransactionAlerts.js
@@ -0,0 +1,41 @@
+import React from 'react'
+import * as R from 'ramda'
+
+import { HIGH_VALUE_TRANSACTION_KEY, isDisabled } from './aux.js'
+import { BigNumericInput } from './Inputs'
+
+const TransactionAlerts = ({
+ value: setupValue,
+ save,
+ editingState,
+ handleEditingClick,
+ setError
+}) => {
+ const editing = editingState[HIGH_VALUE_TRANSACTION_KEY]
+
+ const handleEdit = R.curry(handleEditingClick)
+
+ const handleSubmit = it => save(it)
+
+ const field = {
+ name: HIGH_VALUE_TRANSACTION_KEY,
+ label: 'Alert me over',
+ value: setupValue[HIGH_VALUE_TRANSACTION_KEY]
+ }
+
+ return (
+
+
+
+ )
+}
+
+export default TransactionAlerts
diff --git a/new-lamassu-admin/src/pages/Notifications/aux.js b/new-lamassu-admin/src/pages/Notifications/aux.js
new file mode 100644
index 00000000..b2f1670d
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Notifications/aux.js
@@ -0,0 +1,61 @@
+import * as R from 'ramda'
+
+const EMAIL_KEY = 'email'
+const SMS_KEY = 'sms'
+const BALANCE_KEY = 'balance'
+const TRANSACTIONS_KEY = 'transactions'
+const COMPLIANCE_KEY = 'compliance'
+const SECURITY_KEY = 'security'
+const ERRORS_KEY = 'errors'
+const ACTIVE_KEY = 'active'
+const SETUP_KEY = 'setup'
+const CHANNEL_KEY = 'channel'
+const TRANSACTION_ALERTS_KEY = 'transactionAlerts'
+const HIGH_VALUE_TRANSACTION_KEY = 'highValueTransaction'
+const FIAT_BALANCE_ALERTS_KEY = 'fiatBalanceAlerts'
+const CASH_IN_FULL_KEY = 'cashInFull'
+const CASH_OUT_EMPTY_KEY = 'cashOutEmpty'
+const MACHINE_KEY = 'machine'
+const PERCENTAGE_KEY = 'percentage'
+const NUMERARY_KEY = 'numerary'
+const CASSETTE_1_KEY = 'cassete1'
+const CASSETTE_2_KEY = 'cassete2'
+const OVERRIDES_KEY = 'overrides'
+const CRYPTO_BALANCE_ALERTS_KEY = 'cryptoBalanceAlerts'
+const LOW_BALANCE_KEY = 'lowBalance'
+const HIGH_BALANCE_KEY = 'highBalance'
+const ADD_OVERRIDE_FBA_KEY = 'addOverrideFBA'
+const ADD_OVERRIDE_CBA_KEY = 'addOverrideCBA'
+
+const isDisabled = (state, self) =>
+ R.any(x => x === true, R.values(R.omit([self], state)))
+
+export {
+ EMAIL_KEY,
+ SMS_KEY,
+ BALANCE_KEY,
+ TRANSACTIONS_KEY,
+ COMPLIANCE_KEY,
+ SECURITY_KEY,
+ ERRORS_KEY,
+ ACTIVE_KEY,
+ SETUP_KEY,
+ CHANNEL_KEY,
+ TRANSACTION_ALERTS_KEY,
+ HIGH_VALUE_TRANSACTION_KEY,
+ FIAT_BALANCE_ALERTS_KEY,
+ CASH_IN_FULL_KEY,
+ CASH_OUT_EMPTY_KEY,
+ MACHINE_KEY,
+ PERCENTAGE_KEY,
+ NUMERARY_KEY,
+ CASSETTE_1_KEY,
+ CASSETTE_2_KEY,
+ OVERRIDES_KEY,
+ CRYPTO_BALANCE_ALERTS_KEY,
+ LOW_BALANCE_KEY,
+ HIGH_BALANCE_KEY,
+ ADD_OVERRIDE_FBA_KEY,
+ ADD_OVERRIDE_CBA_KEY,
+ isDisabled
+}
diff --git a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js
index 16574e2e..b9044ec9 100644
--- a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js
+++ b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js
@@ -53,6 +53,7 @@ const OperatorInfo = () => {
{isSelected(CONTACT_INFORMATION) && }
{isSelected(TERMS_CONDITIONS) && }
{isSelected(COIN_ATM_RADAR) && }
+ {isSelected(TERMS_CONDITIONS) && }