diff --git a/new-lamassu-admin/src/components/ErrorMessage.js b/new-lamassu-admin/src/components/ErrorMessage.js index 2d95bbb4..8a1c891b 100644 --- a/new-lamassu-admin/src/components/ErrorMessage.js +++ b/new-lamassu-admin/src/components/ErrorMessage.js @@ -1,6 +1,6 @@ -import React from 'react' -import classnames from 'classnames' import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' +import React from 'react' import { ReactComponent as ErrorIcon } from 'src/styling/icons/warning-icon/tomato.svg' import { errorColor } from 'src/styling/variables' @@ -20,8 +20,7 @@ const styles = { alignItems: 'center', color: errorColor, margin: 0, - whiteSpace: 'break-spaces', - width: 250 + whiteSpace: 'break-spaces' } } diff --git a/new-lamassu-admin/src/components/Modal.js b/new-lamassu-admin/src/components/Modal.js new file mode 100644 index 00000000..048586d1 --- /dev/null +++ b/new-lamassu-admin/src/components/Modal.js @@ -0,0 +1,54 @@ +import { makeStyles, Modal as MaterialModal, Paper } from '@material-ui/core' +import classnames from 'classnames' +import React from 'react' + +import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg' + +const styles = { + modal: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center' + }, + modalContentWrapper: { + display: 'flex', + position: 'relative', + minHeight: 400, + maxHeight: '90vh', + overflowY: 'auto', + borderRadius: 8, + outline: 0, + '& > div': { + width: '100%' + } + }, + closeIcon: { + position: 'absolute', + width: 18, + height: 18, + padding: 0, + top: 20, + right: 20 + } +} + +const useStyles = makeStyles(styles) + +const Modal = ({ handleClose, children, className, ...props }) => { + const classes = useStyles() + + return ( + + + + {children} + + + ) +} + +export default Modal diff --git a/new-lamassu-admin/src/components/Stage.js b/new-lamassu-admin/src/components/Stage.js new file mode 100644 index 00000000..916daf4e --- /dev/null +++ b/new-lamassu-admin/src/components/Stage.js @@ -0,0 +1,112 @@ +import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' +import * as R from 'ramda' +import React, { memo } from 'react' + +import { ReactComponent as CompleteStageIconSpring } from 'src/styling/icons/stage/spring/complete.svg' +import { ReactComponent as CurrentStageIconSpring } from 'src/styling/icons/stage/spring/current.svg' +import { ReactComponent as EmptyStageIconSpring } from 'src/styling/icons/stage/spring/empty.svg' +import { ReactComponent as CompleteStageIconZodiac } from 'src/styling/icons/stage/zodiac/complete.svg' +import { ReactComponent as CurrentStageIconZodiac } from 'src/styling/icons/stage/zodiac/current.svg' +import { ReactComponent as EmptyStageIconZodiac } from 'src/styling/icons/stage/zodiac/empty.svg' +import { + primaryColor, + secondaryColor, + offColor, + disabledColor +} from 'src/styling/variables' + +const styles = { + stages: { + display: 'flex', + alignItems: 'center' + }, + wrapper: { + display: 'flex', + alignItems: 'center', + margin: 0 + }, + stage: { + display: 'flex', + height: 28, + width: 28, + zIndex: 2, + '& > svg': { + height: '100%', + width: '100%', + overflow: 'visible' + } + }, + separator: { + width: 28, + height: 2, + border: [[2, 'solid']], + zIndex: 1 + }, + separatorSpring: { + borderColor: secondaryColor + }, + separatorZodiac: { + borderColor: primaryColor + }, + separatorSpringEmpty: { + borderColor: disabledColor + }, + separatorZodiacEmpty: { + borderColor: offColor + } +} + +const useStyles = makeStyles(styles) + +const Stage = memo(({ stages, currentStage, color = 'spring', className }) => { + if (currentStage < 1 || currentStage > stages) + throw Error('Value of currentStage is invalid') + if (stages < 1) throw Error('Value of stages is invalid') + + const classes = useStyles() + + const separatorClasses = { + [classes.separator]: true, + [classes.separatorSpring]: color === 'spring', + [classes.separatorZodiac]: color === 'zodiac' + } + + const separatorEmptyClasses = { + [classes.separator]: true, + [classes.separatorSpringEmpty]: color === 'spring', + [classes.separatorZodiacEmpty]: color === 'zodiac' + } + + return ( +
+ {R.range(1, currentStage).map(idx => ( +
+ {idx > 1 &&
} +
+ {color === 'spring' && } + {color === 'zodiac' && } +
+
+ ))} +
+ {currentStage > 1 &&
} +
+ {color === 'spring' && } + {color === 'zodiac' && } +
+
+ {R.range(currentStage + 1, stages + 1).map(idx => ( +
+
+
+ {color === 'spring' && } + {color === 'zodiac' && } +
+
+ ))} +
+ ) +}) + +export default Stage diff --git a/new-lamassu-admin/src/components/inputs/autocomplete/Autocomplete.js b/new-lamassu-admin/src/components/inputs/autocomplete/Autocomplete.js new file mode 100644 index 00000000..4f5a5d5c --- /dev/null +++ b/new-lamassu-admin/src/components/inputs/autocomplete/Autocomplete.js @@ -0,0 +1,121 @@ +import Paper from '@material-ui/core/Paper' +import Popper from '@material-ui/core/Popper' +import { withStyles } from '@material-ui/core/styles' +import Downshift from 'downshift' +import * as R from 'ramda' +import React, { memo, useState } from 'react' + +import { + renderInput, + renderSuggestion, + filterSuggestions, + styles +} from './commons' + +const Autocomplete = memo( + ({ + suggestions, + classes, + placeholder, + label, + itemToString, + code = 'code', + display = 'display', + ...props + }) => { + const { name, value, onBlur } = props.field + const { touched, errors, setFieldValue } = props.form + + const [popperNode, setPopperNode] = useState(null) + + return ( + { + if (itemToString) return itemToString(it) + if (it) return it[display] + return undefined + }} + onChange={it => setFieldValue(name, it)} + defaultHighlightedIndex={0} + selectedItem={value}> + {({ + getInputProps, + getItemProps, + getMenuProps, + isOpen, + inputValue: inputValue2, + selectedItem: selectedItem2, + highlightedIndex, + inputValue, + toggleMenu, + clearSelection + }) => ( +
+ {renderInput({ + name, + fullWidth: true, + error: + (touched[`${name}-input`] || touched[name]) && errors[name], + success: + (touched[`${name}-input`] || touched[name] || value) && + !errors[name], + InputProps: getInputProps({ + value: inputValue2 || '', + placeholder, + onBlur, + onClick: event => { + setPopperNode(event.currentTarget.parentElement) + toggleMenu() + }, + onChange: it => { + if (it.target.value === '') { + clearSelection() + } + inputValue = it.target.value + } + }), + label + })} + +
+ + {filterSuggestions( + suggestions, + inputValue2, + value ? R.of(value) : [], + code, + display + ).map((suggestion, index) => + renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion }), + highlightedIndex, + selectedItem: selectedItem2, + code, + display + }) + )} + +
+
+
+ )} +
+ ) + } +) + +export default withStyles(styles)(Autocomplete) diff --git a/new-lamassu-admin/src/components/inputs/autocomplete/AutocompleteSelect.js b/new-lamassu-admin/src/components/inputs/autocomplete/AutocompleteSelect.js new file mode 100644 index 00000000..e67c78f5 --- /dev/null +++ b/new-lamassu-admin/src/components/inputs/autocomplete/AutocompleteSelect.js @@ -0,0 +1,121 @@ +import Paper from '@material-ui/core/Paper' +import Popper from '@material-ui/core/Popper' +import { withStyles } from '@material-ui/core/styles' +import classnames from 'classnames' +import Downshift from 'downshift' +import * as R from 'ramda' +import React, { memo, useState } from 'react' + +import { + renderInput, + renderSuggestion, + filterSuggestions, + styles +} from './commons' + +const AutocompleteSelect = memo( + ({ + suggestions, + classes, + placeholder, + label, + itemToString, + code = 'code', + display = 'display', + name, + value, + touched, + error, + handleChange, + className, + ...props + }) => { + const [popperNode, setPopperNode] = useState(null) + + return ( + { + if (itemToString) return itemToString(it) + if (it) return it[display] + return undefined + }} + onChange={handleChange} + defaultHighlightedIndex={0} + selectedItem={value}> + {({ + getInputProps, + getItemProps, + getMenuProps, + isOpen, + inputValue: inputValue2, + selectedItem: selectedItem2, + highlightedIndex, + inputValue, + toggleMenu, + clearSelection + }) => ( +
+ {renderInput({ + name, + fullWidth: true, + error: touched && error, + success: touched && !error, + InputProps: getInputProps({ + value: inputValue2 || '', + placeholder, + onClick: event => { + setPopperNode(event.currentTarget.parentElement) + toggleMenu() + }, + onChange: it => { + if (it.target.value === '') { + clearSelection() + } + inputValue = it.target.value + } + }), + label + })} + +
+ + {filterSuggestions( + suggestions, + inputValue2, + value ? R.of(value) : [], + code, + display + ).map((suggestion, index) => + renderSuggestion({ + suggestion, + index, + itemProps: getItemProps({ item: suggestion }), + highlightedIndex, + selectedItem: selectedItem2, + code, + display + }) + )} + +
+
+
+ )} +
+ ) + } +) + +export default withStyles(styles)(AutocompleteSelect) diff --git a/new-lamassu-admin/src/components/inputs/autocomplete/commons.js b/new-lamassu-admin/src/components/inputs/autocomplete/commons.js new file mode 100644 index 00000000..7f0353c7 --- /dev/null +++ b/new-lamassu-admin/src/components/inputs/autocomplete/commons.js @@ -0,0 +1,132 @@ +import MenuItem from '@material-ui/core/MenuItem' +import { withStyles } from '@material-ui/core/styles' +import Fuse from 'fuse.js' +import React from 'react' +import slugify from 'slugify' + +import { + fontColor, + inputFontSize, + inputFontWeight, + zircon +} from 'src/styling/variables' +import S from 'src/utils/sanctuary' + +import { TextInput } from '../base' + +function renderInput({ InputProps, error, name, success, ...props }) { + const { onChange, onBlur, value } = InputProps + + return ( + + ) +} + +function renderSuggestion({ + suggestion, + index, + itemProps, + highlightedIndex, + selectedItem, + code, + display +}) { + const isHighlighted = highlightedIndex === index + + const StyledMenuItem = withStyles(theme => ({ + root: { + fontSize: 14, + fontWeight: 400, + color: fontColor + }, + selected: { + '&.Mui-selected, &.Mui-selected:hover': { + fontWeight: 500, + backgroundColor: zircon + } + } + }))(MenuItem) + + return ( + + {suggestion[display]} + + ) +} + +function filterSuggestions( + suggestions = [], + value = '', + currentValues = [], + code, + display +) { + const options = { + shouldSort: true, + threshold: 0.2, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: [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 amountToTake = S.min(filtered.length)(5) + + return S.compose(S.fromMaybe([]))(S.take(amountToTake))(filtered) +} + +const styles = theme => ({ + root: { + flexGrow: 1, + height: 250 + }, + container: { + flexGrow: 1, + position: 'relative' + }, + paper: { + // position: 'absolute', + zIndex: 1, + marginTop: theme.spacing(1), + left: 0, + right: 0 + }, + inputRoot: { + fontSize: inputFontSize, + color: fontColor, + fontWeight: inputFontWeight, + flexWrap: 'wrap' + }, + inputInput: { + flex: 1 + }, + success: { + '&:after': { + transform: 'scaleX(1)' + } + }, + divider: { + height: theme.spacing(2) + } +}) + +export { renderInput, renderSuggestion, filterSuggestions, styles } diff --git a/new-lamassu-admin/src/components/inputs/base/RadioGroup.js b/new-lamassu-admin/src/components/inputs/base/RadioGroup.js index a6953dd9..f1635634 100644 --- a/new-lamassu-admin/src/components/inputs/base/RadioGroup.js +++ b/new-lamassu-admin/src/components/inputs/base/RadioGroup.js @@ -15,6 +15,7 @@ const { p } = typographyStyles const GreenRadio = withStyles({ root: { color: secondaryColor, + padding: [[9, 8, 9, 9]], '&$checked': { color: secondaryColor } diff --git a/new-lamassu-admin/src/components/inputs/formik/SecretInput.js b/new-lamassu-admin/src/components/inputs/formik/SecretInput.js index 214da002..9fac6534 100644 --- a/new-lamassu-admin/src/components/inputs/formik/SecretInput.js +++ b/new-lamassu-admin/src/components/inputs/formik/SecretInput.js @@ -1,13 +1,13 @@ -import React, { memo, useState } from 'react' -import classnames from 'classnames' import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' +import React, { memo, useState } from 'react' import TextInputFormik from './TextInput' import { styles } from './TextInput.styles' const useStyles = makeStyles(styles) -const SecretInputFormik = memo(({ ...props }) => { +const SecretInputFormik = memo(({ className, ...props }) => { const { value } = props.field const classes = useStyles() @@ -37,7 +37,7 @@ const SecretInputFormik = memo(({ ...props }) => { ) diff --git a/new-lamassu-admin/src/components/inputs/index.js b/new-lamassu-admin/src/components/inputs/index.js index 3bb66f0e..44389d60 100644 --- a/new-lamassu-admin/src/components/inputs/index.js +++ b/new-lamassu-admin/src/components/inputs/index.js @@ -1,3 +1,4 @@ +import AutocompleteSelect from './autocomplete/AutocompleteSelect' import Checkbox from './base/Checkbox' import Radio from './base/Radio' import RadioGroup from './base/RadioGroup' @@ -5,4 +6,12 @@ import Select from './base/Select' import Switch from './base/Switch' import TextInput from './base/TextInput' -export { TextInput, Radio, Checkbox, Switch, Select, RadioGroup } +export { + AutocompleteSelect, + TextInput, + Radio, + Checkbox, + Switch, + Select, + RadioGroup +} diff --git a/new-lamassu-admin/src/components/layout/TitleSection.styles.js b/new-lamassu-admin/src/components/layout/TitleSection.styles.js index d36ac905..db9958b5 100644 --- a/new-lamassu-admin/src/components/layout/TitleSection.styles.js +++ b/new-lamassu-admin/src/components/layout/TitleSection.styles.js @@ -7,5 +7,11 @@ export default { }, titleAndButtonsContainer: { display: 'flex' + }, + iconButton: { + border: 'none', + outline: 0, + backgroundColor: 'transparent', + cursor: 'pointer' } } diff --git a/new-lamassu-admin/src/pages/Services/Bitgo.js b/new-lamassu-admin/src/pages/Services/Bitgo.js index db92be4a..133d5705 100644 --- a/new-lamassu-admin/src/pages/Services/Bitgo.js +++ b/new-lamassu-admin/src/pages/Services/Bitgo.js @@ -81,10 +81,9 @@ const BitgoCard = memo(({ account, onEdit, ...props }) => { ) }) -const BitgoForm = ({ account, handleSubmit, ...props }) => { +const getBitgoFormik = account => { const getValue = getValueAux(account) - const { code } = account const token = getValue(schema.token.code) const btcWalletId = getValue(schema.btcWalletId.code) const btcWalletPassphrase = getValue(schema.btcWalletPassphrase.code) @@ -98,7 +97,7 @@ const BitgoForm = ({ account, handleSubmit, ...props }) => { const dashWalletPassphrase = getValue(schema.dashWalletPassphrase.code) const environment = getValue(schema.environment.code) - const formik = { + return { initialValues: { token: token, BTCWalletId: btcWalletId, @@ -157,82 +156,90 @@ const BitgoForm = ({ account, handleSubmit, ...props }) => { return errors } } +} - const fields = [ - { - name: schema.token.code, - label: schema.token.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.btcWalletId.code, - label: schema.btcWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.btcWalletPassphrase.code, - label: schema.btcWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.ltcWalletId.code, - label: schema.ltcWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.ltcWalletPassphrase.code, - label: schema.ltcWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.zecWalletId.code, - label: schema.zecWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.zecWalletPassphrase.code, - label: schema.zecWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.bchWalletId.code, - label: schema.bchWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.bchWalletPassphrase.code, - label: schema.bchWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.dashWalletId.code, - label: schema.dashWalletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.dashWalletPassphrase.code, - label: schema.dashWalletPassphrase.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.environment.code, - label: schema.environment.display, - placeholder: 'prod or test', - type: 'text', - component: TextInputFormik - } - ] +const getBitgoFields = () => [ + { + name: schema.token.code, + label: schema.token.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.btcWalletId.code, + label: schema.btcWalletId.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.btcWalletPassphrase.code, + label: schema.btcWalletPassphrase.display, + type: 'text', + component: SecretInputFormik + }, + { + name: schema.ltcWalletId.code, + label: schema.ltcWalletId.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.ltcWalletPassphrase.code, + label: schema.ltcWalletPassphrase.display, + type: 'text', + component: SecretInputFormik + }, + { + name: schema.zecWalletId.code, + label: schema.zecWalletId.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.zecWalletPassphrase.code, + label: schema.zecWalletPassphrase.display, + type: 'text', + component: SecretInputFormik + }, + { + name: schema.bchWalletId.code, + label: schema.bchWalletId.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.bchWalletPassphrase.code, + label: schema.bchWalletPassphrase.display, + type: 'text', + component: SecretInputFormik + }, + { + name: schema.dashWalletId.code, + label: schema.dashWalletId.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.dashWalletPassphrase.code, + label: schema.dashWalletPassphrase.display, + type: 'text', + component: SecretInputFormik + }, + { + name: schema.environment.code, + label: schema.environment.display, + placeholder: 'prod or test', + type: 'text', + component: TextInputFormik + } +] + +const BitgoForm = ({ account, handleSubmit, ...props }) => { + const { code } = account + + const formik = getBitgoFormik(account) + + const fields = getBitgoFields() return ( <> @@ -247,4 +254,4 @@ const BitgoForm = ({ account, handleSubmit, ...props }) => { ) } -export { BitgoForm, BitgoCard } +export { BitgoForm, BitgoCard, getBitgoFormik, getBitgoFields } diff --git a/new-lamassu-admin/src/pages/Services/Bitstamp.js b/new-lamassu-admin/src/pages/Services/Bitstamp.js index b50e8023..8216f632 100644 --- a/new-lamassu-admin/src/pages/Services/Bitstamp.js +++ b/new-lamassu-admin/src/pages/Services/Bitstamp.js @@ -52,15 +52,14 @@ const BitstampCard = memo(({ account, onEdit, ...props }) => { ) }) -const BitstampForm = ({ account, ...props }) => { +const getBitstampFormik = account => { const getValue = getValueAux(account) - const { code } = account const clientId = getValue(schema.clientId.code) const key = getValue(schema.key.code) const secret = getValue(schema.secret.code) - const formik = { + return { initialValues: { clientId: clientId, key: key, @@ -78,27 +77,35 @@ const BitstampForm = ({ account, ...props }) => { .required('Required') }) } +} - const fields = [ - { - name: schema.clientId.code, - label: schema.clientId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.key.code, - label: schema.key.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.secret.code, - label: schema.secret.display, - type: 'text', - component: SecretInputFormik - } - ] +const getBitstampFields = () => [ + { + name: schema.clientId.code, + label: schema.clientId.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.key.code, + label: schema.key.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.secret.code, + label: schema.secret.display, + type: 'text', + component: SecretInputFormik + } +] + +const BitstampForm = ({ account, ...props }) => { + const { code } = account + + const formik = getBitstampFormik(account) + + const fields = getBitstampFields() return ( <> @@ -113,4 +120,4 @@ const BitstampForm = ({ account, ...props }) => { ) } -export { BitstampForm, BitstampCard } +export { BitstampForm, BitstampCard, getBitstampFormik, getBitstampFields } diff --git a/new-lamassu-admin/src/pages/Services/Blockcypher.js b/new-lamassu-admin/src/pages/Services/Blockcypher.js index e64b004f..cb3ad18d 100644 --- a/new-lamassu-admin/src/pages/Services/Blockcypher.js +++ b/new-lamassu-admin/src/pages/Services/Blockcypher.js @@ -47,14 +47,13 @@ const BlockcypherCard = memo(({ account, onEdit, ...props }) => { ) }) -const BlockcypherForm = ({ account, ...props }) => { +const getBlockcypherFormik = account => { const getValue = getValueAux(account) - const { code } = account const token = getValue(schema.token.code) const confidenceFactor = getValue(schema.confidenceFactor.code) - const formik = { + return { initialValues: { token: token, confidenceFactor: confidenceFactor @@ -69,21 +68,29 @@ const BlockcypherForm = ({ account, ...props }) => { .required('Required') }) } +} - const fields = [ - { - name: schema.token.code, - label: schema.token.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.confidenceFactor.code, - label: schema.confidenceFactor.display, - type: 'text', - component: TextInputFormik - } - ] +const getBlockcypherFields = () => [ + { + name: schema.token.code, + label: schema.token.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.confidenceFactor.code, + label: schema.confidenceFactor.display, + type: 'text', + component: TextInputFormik + } +] + +const BlockcypherForm = ({ account, ...props }) => { + const { code } = account + + const formik = getBlockcypherFormik(account) + + const fields = getBlockcypherFields() return ( <> @@ -98,4 +105,9 @@ const BlockcypherForm = ({ account, ...props }) => { ) } -export { BlockcypherForm, BlockcypherCard } +export { + BlockcypherForm, + BlockcypherCard, + getBlockcypherFormik, + getBlockcypherFields +} diff --git a/new-lamassu-admin/src/pages/Services/Infura.js b/new-lamassu-admin/src/pages/Services/Infura.js index 76987c2f..47e3aa9b 100644 --- a/new-lamassu-admin/src/pages/Services/Infura.js +++ b/new-lamassu-admin/src/pages/Services/Infura.js @@ -52,15 +52,14 @@ const InfuraCard = memo(({ account, onEdit, ...props }) => { ) }) -const InfuraForm = ({ account, ...props }) => { +const getInfuraFormik = account => { const getValue = getValueAux(account) - const { code } = account const apiKey = getValue(schema.apiKey.code) const apiSecret = getValue(schema.apiSecret.code) const endpoint = getValue(schema.endpoint.code) - const formik = { + return { initialValues: { apiKey: apiKey, apiSecret: apiSecret, @@ -79,8 +78,10 @@ const InfuraForm = ({ account, ...props }) => { .required('Required') }) } +} - const fields = [ +const getInfuraFields = () => { + return [ { name: schema.apiKey.code, label: schema.apiKey.display, @@ -100,6 +101,14 @@ const InfuraForm = ({ account, ...props }) => { component: TextInputFormik } ] +} + +const InfuraForm = ({ account, ...props }) => { + const { code } = account + + const formik = getInfuraFormik(account) + + const fields = getInfuraFields() return ( <> @@ -114,4 +123,4 @@ const InfuraForm = ({ account, ...props }) => { ) } -export { InfuraCard, InfuraForm } +export { InfuraCard, InfuraForm, getInfuraFormik, getInfuraFields } diff --git a/new-lamassu-admin/src/pages/Services/Itbit.js b/new-lamassu-admin/src/pages/Services/Itbit.js index 7b7dee6c..23371a4e 100644 --- a/new-lamassu-admin/src/pages/Services/Itbit.js +++ b/new-lamassu-admin/src/pages/Services/Itbit.js @@ -51,16 +51,15 @@ const ItbitCard = memo(({ account, onEdit, ...props }) => { ) }) -const ItbitForm = ({ account, ...props }) => { +const getItbitFormik = account => { const getValue = getValueAux(account) - const { code } = account const userId = getValue(schema.userId.code) const walletId = getValue(schema.walletId.code) const clientKey = getValue(schema.clientKey.code) const clientSecret = getValue(schema.clientSecret.code) - const formik = { + return { initialValues: { userId: userId, walletId: walletId, @@ -82,33 +81,41 @@ const ItbitForm = ({ account, ...props }) => { .required('Required') }) } +} - const fields = [ - { - name: schema.userId.code, - label: schema.userId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.walletId.code, - label: schema.walletId.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.clientKey.code, - label: schema.clientKey.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.clientSecret.code, - label: schema.clientSecret.display, - type: 'text', - component: SecretInputFormik - } - ] +const getItbitFields = () => [ + { + name: schema.userId.code, + label: schema.userId.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.walletId.code, + label: schema.walletId.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.clientKey.code, + label: schema.clientKey.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.clientSecret.code, + label: schema.clientSecret.display, + type: 'text', + component: SecretInputFormik + } +] + +const ItbitForm = ({ account, ...props }) => { + const { code } = account + + const formik = getItbitFormik(account) + + const fields = getItbitFields() return ( <> @@ -123,4 +130,4 @@ const ItbitForm = ({ account, ...props }) => { ) } -export { ItbitCard, ItbitForm } +export { ItbitCard, ItbitForm, getItbitFormik, getItbitFields } diff --git a/new-lamassu-admin/src/pages/Services/Kraken.js b/new-lamassu-admin/src/pages/Services/Kraken.js index 7b1fa2dc..7587d849 100644 --- a/new-lamassu-admin/src/pages/Services/Kraken.js +++ b/new-lamassu-admin/src/pages/Services/Kraken.js @@ -48,14 +48,13 @@ const KrakenCard = memo(({ account, onEdit, ...props }) => { ) }) -const KrakenForm = ({ account, ...props }) => { +const getKrakenFormik = account => { const getValue = getValueAux(account) - const { code } = account const apiKey = getValue(schema.apiKey.code) const privateKey = getValue(schema.privateKey.code) - const formik = { + return { initialValues: { apiKey: apiKey, privateKey: privateKey @@ -69,21 +68,29 @@ const KrakenForm = ({ account, ...props }) => { .required('Required') }) } +} - const fields = [ - { - name: schema.apiKey.code, - label: schema.apiKey.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.privateKey.code, - label: schema.privateKey.display, - type: 'text', - component: SecretInputFormik - } - ] +const getKrakenFields = () => [ + { + name: schema.apiKey.code, + label: schema.apiKey.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.privateKey.code, + label: schema.privateKey.display, + type: 'text', + component: SecretInputFormik + } +] + +const KrakenForm = ({ account, ...props }) => { + const { code } = account + + const formik = getKrakenFormik(account) + + const fields = getKrakenFields() return ( <> @@ -98,4 +105,4 @@ const KrakenForm = ({ account, ...props }) => { ) } -export { KrakenCard, KrakenForm } +export { KrakenCard, KrakenForm, getKrakenFormik, getKrakenFields } diff --git a/new-lamassu-admin/src/pages/Services/Mailgun.js b/new-lamassu-admin/src/pages/Services/Mailgun.js index d822f839..1347e43f 100644 --- a/new-lamassu-admin/src/pages/Services/Mailgun.js +++ b/new-lamassu-admin/src/pages/Services/Mailgun.js @@ -55,16 +55,15 @@ const MailgunCard = memo(({ account, onEdit, ...props }) => { ) }) -const MailgunForm = ({ account, ...props }) => { +const getMailgunFormik = account => { const getValue = getValueAux(account) - const { code } = account const apiKey = getValue(schema.apiKey.code) const domain = getValue(schema.domain.code) const fromEmail = getValue(schema.fromEmail.code) const toEmail = getValue(schema.toEmail.code) - const formik = { + return { initialValues: { apiKey: apiKey, domain: domain, @@ -88,33 +87,41 @@ const MailgunForm = ({ account, ...props }) => { .required('Required') }) } +} - const fields = [ - { - name: schema.apiKey.code, - label: schema.apiKey.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.domain.code, - label: schema.domain.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.fromEmail.code, - label: schema.fromEmail.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.toEmail.code, - label: schema.toEmail.display, - type: 'text', - component: TextInputFormik - } - ] +const getMailgunFields = () => [ + { + name: schema.apiKey.code, + label: schema.apiKey.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.domain.code, + label: schema.domain.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.fromEmail.code, + label: schema.fromEmail.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.toEmail.code, + label: schema.toEmail.display, + type: 'text', + component: TextInputFormik + } +] + +const MailgunForm = ({ account, ...props }) => { + const { code } = account + + const formik = getMailgunFormik(account) + + const fields = getMailgunFields() return ( <> @@ -129,4 +136,4 @@ const MailgunForm = ({ account, ...props }) => { ) } -export { MailgunCard, MailgunForm } +export { MailgunCard, MailgunForm, getMailgunFormik, getMailgunFields } diff --git a/new-lamassu-admin/src/pages/Services/Strike.js b/new-lamassu-admin/src/pages/Services/Strike.js index d8979cec..e8eef813 100644 --- a/new-lamassu-admin/src/pages/Services/Strike.js +++ b/new-lamassu-admin/src/pages/Services/Strike.js @@ -37,13 +37,12 @@ const StrikeCard = memo(({ account, onEdit, ...props }) => { ) }) -const StrikeForm = ({ account, ...props }) => { +const getStrikeFormik = account => { const getValue = getValueAux(account) - const code = 'strike' const token = getValue(schema.token.code) - const formik = { + return { initialValues: { token: token }, @@ -53,15 +52,23 @@ const StrikeForm = ({ account, ...props }) => { .required('Required') }) } +} - const fields = [ - { - name: schema.token.code, - label: schema.token.display, - type: 'text', - component: SecretInputFormik - } - ] +const getStrikeFields = () => [ + { + name: schema.token.code, + label: schema.token.display, + type: 'text', + component: SecretInputFormik + } +] + +const StrikeForm = ({ account, ...props }) => { + const code = 'strike' + + const formik = getStrikeFormik(account) + + const fields = getStrikeFields() return ( <> @@ -76,4 +83,4 @@ const StrikeForm = ({ account, ...props }) => { ) } -export { StrikeCard, StrikeForm } +export { StrikeCard, StrikeForm, getStrikeFormik, getStrikeFields } diff --git a/new-lamassu-admin/src/pages/Services/Twilio.js b/new-lamassu-admin/src/pages/Services/Twilio.js index 16e16616..15a6c5ae 100644 --- a/new-lamassu-admin/src/pages/Services/Twilio.js +++ b/new-lamassu-admin/src/pages/Services/Twilio.js @@ -56,16 +56,15 @@ const TwilioCard = memo(({ account, onEdit, ...props }) => { ) }) -const TwilioForm = ({ account, ...props }) => { +const getTwilioFormik = account => { const getValue = getValueAux(account) - const { code } = account const accountSid = getValue(schema.accountSid.code) const authToken = getValue(schema.authToken.code) const fromNumber = getValue(schema.fromNumber.code) const toNumber = getValue(schema.toNumber.code) - const formik = { + return { initialValues: { accountSid: accountSid, authToken: authToken, @@ -87,33 +86,41 @@ const TwilioForm = ({ account, ...props }) => { .required('Required') }) } +} - const fields = [ - { - name: schema.accountSid.code, - label: schema.accountSid.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.authToken.code, - label: schema.authToken.display, - type: 'text', - component: SecretInputFormik - }, - { - name: schema.fromNumber.code, - label: schema.fromNumber.display, - type: 'text', - component: TextInputFormik - }, - { - name: schema.toNumber.code, - label: schema.toNumber.display, - type: 'text', - component: TextInputFormik - } - ] +const getTwilioFields = () => [ + { + name: schema.accountSid.code, + label: schema.accountSid.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.authToken.code, + label: schema.authToken.display, + type: 'text', + component: SecretInputFormik + }, + { + name: schema.fromNumber.code, + label: schema.fromNumber.display, + type: 'text', + component: TextInputFormik + }, + { + name: schema.toNumber.code, + label: schema.toNumber.display, + type: 'text', + component: TextInputFormik + } +] + +const TwilioForm = ({ account, ...props }) => { + const { code } = account + + const formik = getTwilioFormik(account) + + const fields = getTwilioFields() return ( <> @@ -128,4 +135,4 @@ const TwilioForm = ({ account, ...props }) => { ) } -export { TwilioCard, TwilioForm } +export { TwilioCard, TwilioForm, getTwilioFormik, getTwilioFields } diff --git a/new-lamassu-admin/src/pages/Services/aux.js b/new-lamassu-admin/src/pages/Services/aux.js index 032534a9..bdb03ddf 100644 --- a/new-lamassu-admin/src/pages/Services/aux.js +++ b/new-lamassu-admin/src/pages/Services/aux.js @@ -1,10 +1,10 @@ -import React from 'react' -import * as R from 'ramda' import { makeStyles } from '@material-ui/core' +import * as R from 'ramda' +import React from 'react' import SingleRowTable from 'src/components/single-row-table/SingleRowTable' -const getValue = R.curry((account, code) => account[code] ?? '') +const getValue = R.curry((account, code) => (account ? account[code] : '')) const formatLong = value => { if (!value) return '' diff --git a/new-lamassu-admin/src/pages/Wallet/WalletSettings.js b/new-lamassu-admin/src/pages/Wallet/WalletSettings.js new file mode 100644 index 00000000..4baed6e4 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wallet/WalletSettings.js @@ -0,0 +1,451 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import { gql } from 'apollo-boost' +import * as R from 'ramda' +import React, { useState } from 'react' + +import ErrorMessage from 'src/components/ErrorMessage' +import Modal from 'src/components/Modal' +import Title from 'src/components/Title' +import { + Table, + THead, + Th, + TBody, + Tr, + Td +} from 'src/components/fake-table/Table' +import { Switch } from 'src/components/inputs' +import { ReactComponent as DisabledEditIcon } from 'src/styling/icons/action/edit/disabled.svg' +import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg' +import { zircon } from 'src/styling/variables' + +import Wizard from './Wizard' +import WizardSplash from './WizardSplash' +import { + CRYPTOCURRENCY_KEY, + TICKER_KEY, + WALLET_KEY, + EXCHANGE_KEY, + ZERO_CONF_KEY, + EDIT_KEY, + ENABLE_KEY, + SIZE_KEY, + TEXT_ALIGN_KEY +} from './aux.js' + +const styles = { + disabledDrawing: { + position: 'relative', + display: 'flex', + alignItems: 'center', + '& > div': { + position: 'absolute', + backgroundColor: zircon, + height: 36, + width: 678 + } + }, + modal: { + width: 544 + }, + switchErrorMessage: { + margin: [['auto', 0, 'auto', 20]] + } +} + +const useStyles = makeStyles(styles) + +const columns = { + [CRYPTOCURRENCY_KEY]: { + [SIZE_KEY]: 182, + [TEXT_ALIGN_KEY]: 'left' + }, + [TICKER_KEY]: { + [SIZE_KEY]: 182, + [TEXT_ALIGN_KEY]: 'left' + }, + [WALLET_KEY]: { + [SIZE_KEY]: 182, + [TEXT_ALIGN_KEY]: 'left' + }, + [EXCHANGE_KEY]: { + [SIZE_KEY]: 182, + [TEXT_ALIGN_KEY]: 'left' + }, + [ZERO_CONF_KEY]: { + [SIZE_KEY]: 229, + [TEXT_ALIGN_KEY]: 'left' + }, + [EDIT_KEY]: { + [SIZE_KEY]: 134, + [TEXT_ALIGN_KEY]: 'center' + }, + [ENABLE_KEY]: { + [SIZE_KEY]: 109, + [TEXT_ALIGN_KEY]: 'center' + } +} + +const GET_INFO = gql` + { + config + accounts { + code + display + class + cryptos + } + cryptoCurrencies { + code + display + } + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` + +const schema = { + [TICKER_KEY]: '', + [WALLET_KEY]: '', + [EXCHANGE_KEY]: '', + [ZERO_CONF_KEY]: '', + [ENABLE_KEY]: false +} + +const WalletSettings = () => { + const [cryptoCurrencies, setCryptoCurrencies] = useState(null) + const [accounts, setAccounts] = useState(null) + const [services, setServices] = useState(null) + const [state, setState] = useState(null) + const [modalContent, setModalContent] = useState(null) + const [modalOpen, setModalOpen] = useState(false) + const [error, setError] = useState(null) + const [saveConfig] = useMutation(SAVE_CONFIG, { + onCompleted: data => { + setServices(data.saveConfig.accounts) + setError(null) + } + }) + + useQuery(GET_INFO, { + onCompleted: data => { + const { cryptoCurrencies, config, accounts } = data + + const wallet = config?.wallet ?? [] + const services = config?.accounts ?? {} + + const newState = R.map(crypto => { + const el = R.find(R.propEq(CRYPTOCURRENCY_KEY, crypto.code))(wallet) + if (!el) return R.assoc(CRYPTOCURRENCY_KEY, crypto.code)(schema) + return el + })(cryptoCurrencies) + + setState(newState) + setCryptoCurrencies(cryptoCurrencies) + setAccounts(accounts) + setServices(services) + }, + onError: error => console.error(error) + }) + + const classes = useStyles() + + const getSize = key => columns[key][SIZE_KEY] + const getTextAlign = key => columns[key][TEXT_ALIGN_KEY] + + const getDisplayName = list => code => + R.path(['display'], R.find(R.propEq('code', code), list)) + + const getCryptoDisplayName = row => + getDisplayName(cryptoCurrencies)(row[CRYPTOCURRENCY_KEY]) + + const getNoSetUpNeeded = accounts => { + const needs = [ + 'bitgo', + 'bitstamp', + 'blockcypher', + 'infura', + 'kraken', + 'strike' + ] + return R.filter(account => !R.includes(account.code, needs), accounts) + } + const getAlreadySetUp = serviceClass => cryptocode => { + const possible = R.filter( + service => R.includes(cryptocode, service.cryptos), + R.filter(R.propEq('class', serviceClass), accounts) + ) + const isSetUp = service => R.includes(service.code, R.keys(services)) + const alreadySetUp = R.filter(isSetUp, possible) + const join = [...alreadySetUp, ...getNoSetUpNeeded(possible)] + return R.isEmpty(join) ? null : join + } + const getNotSetUp = serviceClass => cryptocode => { + const possible = R.filter( + service => R.includes(cryptocode, service.cryptos), + R.filter(R.propEq('class', serviceClass), accounts) + ) + const without = R.without( + getAlreadySetUp(serviceClass)(cryptocode) ?? [], + possible + ) + return R.isEmpty(without) ? null : without + } + + const saveNewService = (code, it) => { + const newAccounts = R.clone(services) + newAccounts[code] = it + return saveConfig({ variables: { config: { accounts: newAccounts } } }) + } + const save = it => { + const idx = R.findIndex( + R.propEq(CRYPTOCURRENCY_KEY, it[CRYPTOCURRENCY_KEY]), + state + ) + const merged = R.mergeDeepRight(state[idx], it) + const updated = R.update(idx, merged, state) + return saveConfig({ + variables: { config: { wallet: updated } } + }) + } + + const isSet = crypto => + crypto[TICKER_KEY] && + crypto[WALLET_KEY] && + crypto[EXCHANGE_KEY] && + crypto[ZERO_CONF_KEY] + + const handleEnable = row => event => { + if (!isSet(row)) { + setModalContent( + + ) + setModalOpen(true) + setError(null) + return + } + + save(R.assoc(ENABLE_KEY, event.target.checked, row)).catch(error => + setError(error) + ) + } + + const handleEditClick = row => { + setModalOpen(true) + handleModalNavigation(row)(1) + } + + const handleModalClose = () => { + setModalOpen(false) + setModalContent(null) + } + const handleModalNavigation = row => currentPage => { + const cryptocode = row[CRYPTOCURRENCY_KEY] + + switch (currentPage) { + case 1: + setModalContent( + R.includes(cryptocode, ticker.cryptos), + R.filter(R.propEq('class', 'ticker'), accounts) + )} + /> + ) + break + case 2: + setModalContent( + + ) + break + case 3: + setModalContent( + + ) + break + case 4: + setModalContent( + + ) + break + case 5: + // Zero Conf + return save(R.assoc(ENABLE_KEY, true, row)).then(m => { + setModalOpen(false) + setModalContent(null) + }) + default: + break + } + + return new Promise(() => {}) + } + + if (!state) return null + + return ( + <> +
+
+ Wallet Settings + {error && !modalOpen && ( + + Failed to save + + )} +
+
+
+ + + + + + + + + + + + {state.map((row, idx) => ( + + + {!isSet(row) && ( + + )} + {isSet(row) && ( + <> + + + + + + )} + + + + ))} + +
+ Cryptocurrency + + Ticker + + Wallet + + Exchange + + Zero Conf + + Edit + + Enable +
+ {getCryptoDisplayName(row)} + +
+
+ {getDisplayName(accounts)(row[TICKER_KEY])} + + {getDisplayName(accounts)(row[WALLET_KEY])} + + {getDisplayName(accounts)(row[EXCHANGE_KEY])} + + {getDisplayName(accounts)(row[ZERO_CONF_KEY])} + + {!isSet(row) && } + {isSet(row) && ( + + )} + + +
+
+ + {modalContent} + + + ) +} + +export default WalletSettings diff --git a/new-lamassu-admin/src/pages/Wallet/Wizard.js b/new-lamassu-admin/src/pages/Wallet/Wizard.js new file mode 100644 index 00000000..109d72bb --- /dev/null +++ b/new-lamassu-admin/src/pages/Wallet/Wizard.js @@ -0,0 +1,307 @@ +import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' +import { Formik, Field as FormikField } from 'formik' +import * as R from 'ramda' +import React, { useState, useEffect } from 'react' + +import ErrorMessage from 'src/components/ErrorMessage' +import Stage from 'src/components/Stage' +import { Button } from 'src/components/buttons' +import { RadioGroup, AutocompleteSelect } from 'src/components/inputs' +import { H1, Info2, H4 } from 'src/components/typography' +import { startCase } from 'src/utils/string' + +import { getBitgoFields, getBitgoFormik } from '../Services/Bitgo' +import { getBitstampFields, getBitstampFormik } from '../Services/Bitstamp' +import { + getBlockcypherFields, + getBlockcypherFormik +} from '../Services/Blockcypher' +import { getInfuraFields, getInfuraFormik } from '../Services/Infura' +import { getKrakenFields, getKrakenFormik } from '../Services/Kraken' +import { getStrikeFields, getStrikeFormik } from '../Services/Strike' + +const styles = { + modalContent: { + display: 'flex', + flexDirection: 'column', + padding: [[24, 32, 0]], + '& > h1': { + margin: [[0, 0, 10]] + }, + '& > h4': { + margin: [[32, 0, 32 - 9, 0]] + }, + '& > p': { + margin: 0 + } + }, + submitButtonWrapper: { + display: 'flex', + alignSelf: 'flex-end', + margin: [['auto', 0, 0]] + }, + submitButton: { + width: 67, + padding: [[0, 0]], + margin: [['auto', 0, 24, 20]], + '&:active': { + margin: [['auto', 0, 24, 20]] + } + }, + stages: { + marginTop: 10 + }, + radios: { + display: 'flex' + }, + radiosAsColumn: { + flexDirection: 'column' + }, + radiosAsRow: { + flexDirection: 'row' + }, + alreadySetupRadioButtons: { + display: 'flex', + flexDirection: 'row' + }, + selectNewWrapper: { + display: 'flex', + alignItems: 'center' + }, + selectNew: { + width: 204, + flexGrow: 0, + bottom: 7 + }, + newServiceForm: { + display: 'flex', + flexDirection: 'column' + }, + newServiceFormFields: { + marginTop: 20, + marginBottom: 48 + }, + field: { + '&:not(:last-child)': { + marginBottom: 20 + } + }, + formInput: { + '& .MuiInputBase-input': { + width: 426 + } + } +} + +const getNewServiceForm = serviceName => { + switch (serviceName) { + case 'bitgo': + return { fields: getBitgoFields(), formik: getBitgoFormik() } + case 'bitstamp': + return { fields: getBitstampFields(), formik: getBitstampFormik() } + case 'blockcypher': + return { fields: getBlockcypherFields(), formik: getBlockcypherFormik() } + case 'infura': + return { fields: getInfuraFields(), formik: getInfuraFormik() } + case 'kraken': + return { fields: getKrakenFields(), formik: getKrakenFormik() } + case 'strike': + return { fields: getStrikeFields(), formik: getStrikeFormik() } + default: + } +} + +const useStyles = makeStyles(styles) + +const SubmitButton = ({ error, ...props }) => { + const classes = useStyles() + + return ( +
+ {error && Failed to save} + +
+ ) +} + +const Wizard = ({ + crypto, + coinName, + pageName, + currentStage, + alreadySetUp, + notSetUp, + handleModalNavigation, + saveNewService +}) => { + const [selectedRadio, setSelectedRadio] = useState( + crypto[pageName] !== '' ? crypto[pageName] : null + ) + useEffect(() => { + setFormContent(null) + setSelectedFromDropdown(null) + setSetUpNew('') + setSelectedRadio(crypto[pageName] !== '' ? crypto[pageName] : null) + }, [crypto, pageName]) + const [setUpNew, setSetUpNew] = useState(null) + const [selectedFromDropdown, setSelectedFromDropdown] = useState(null) + const [formContent, setFormContent] = useState(null) + const [error, setError] = useState(null) + + const classes = useStyles() + + const radiosClassNames = { + [classes.radios]: true, + [classes.radiosAsColumn]: !selectedFromDropdown, + [classes.radiosAsRow]: selectedFromDropdown + } + + const radioButtonOptions = + alreadySetUp && + R.map(el => { + return { label: el.display, value: el.code } + })(alreadySetUp) + + const handleRadioButtons = event => { + R.o(setSelectedRadio, R.path(['target', 'value']))(event) + setSetUpNew('') + setFormContent(null) + setSelectedFromDropdown(null) + setError(null) + } + + const handleSetUpNew = event => { + R.o(setSetUpNew, R.path(['target', 'value']))(event) + setSelectedRadio('') + setFormContent(null) + setSelectedFromDropdown(null) + setError(null) + } + + const handleNext = value => event => { + const nav = handleModalNavigation( + R.mergeDeepRight(crypto, { [pageName]: value }) + )(currentStage + 1) + + nav.catch(error => setError(error)) + } + + const handleSelectFromDropdown = it => { + setSelectedFromDropdown(it) + setFormContent(getNewServiceForm(it?.code)) + setError(null) + } + + const isSubmittable = () => { + if (selectedRadio) return true + if (!selectedRadio && selectedFromDropdown && !formContent) return true + return false + } + + console.log(formContent) + + return ( +
+

Enable {coinName}

+ {startCase(pageName)} + +

{`Select a ${pageName} or set up a new one`}

+
+ {alreadySetUp && ( + + )} + {notSetUp && ( +
+ + {setUpNew && ( + + )} +
+ )} +
+ {formContent && ( + + saveNewService(selectedFromDropdown.code, values) + .then(m => { + handleNext(selectedFromDropdown.code)() + }) + .catch(error => setError(error)) + }> + {props => ( +
+
+ {formContent.fields.map((field, idx) => ( +
+ { + setError(null) + }} + /> +
+ ))} +
+ + + )} +
+ )} + {!formContent && ( + + )} +
+ ) +} + +export default Wizard diff --git a/new-lamassu-admin/src/pages/Wallet/WizardSplash.js b/new-lamassu-admin/src/pages/Wallet/WizardSplash.js new file mode 100644 index 00000000..40bfa2e8 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wallet/WizardSplash.js @@ -0,0 +1,85 @@ +import { makeStyles } from '@material-ui/core' +import React from 'react' + +import { Button } from 'src/components/buttons' +import { H1, P } from 'src/components/typography' +import { ReactComponent as BitcoinLogo } from 'src/styling/logos/icon-bitcoin-colour.svg' +import { ReactComponent as BitcoinCashLogo } from 'src/styling/logos/icon-bitcoincash-colour.svg' +import { ReactComponent as DashLogo } from 'src/styling/logos/icon-dash-colour.svg' +import { ReactComponent as EthereumLogo } from 'src/styling/logos/icon-ethereum-colour.svg' +import { ReactComponent as LitecoinLogo } from 'src/styling/logos/icon-litecoin-colour.svg' +import { ReactComponent as ZCashLogo } from 'src/styling/logos/icon-zcash-colour.svg' + +const styles = { + logoWrapper: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: 80, + margin: [[40, 0, 24]], + '& > svg': { + maxHeight: '100%', + width: '100%' + } + }, + modalContent: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: [[0, 66]], + '& > h1': { + margin: [[0, 0, 32]] + }, + '& > p': { + margin: 0 + }, + '& > button': { + margin: [['auto', 0, 56]], + '&:active': { + margin: [['auto', 0, 56]] + } + } + } +} + +const useStyles = makeStyles(styles) + +const renderLogo = code => { + switch (code) { + case 'BTC': + return + case 'BCH': + return + case 'DASH': + return + case 'ETH': + return + case 'LTC': + return + case 'ZEC': + return + default: + return null + } +} + +const WizardSplash = ({ code, coinName, handleModalNavigation }) => { + const classes = useStyles() + + return ( +
+
{renderLogo(code)}
+

Enable {coinName}

+

+ You are about to enable {coinName} on your system. This will allow you + to use this cryptocurrency on your machines. To able to do that, you’ll + have to setup all the necessary 3rd party services. +

+ +
+ ) +} + +export default WizardSplash diff --git a/new-lamassu-admin/src/pages/Wallet/aux.js b/new-lamassu-admin/src/pages/Wallet/aux.js new file mode 100644 index 00000000..d8706744 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wallet/aux.js @@ -0,0 +1,21 @@ +const CRYPTOCURRENCY_KEY = 'cryptocurrency' +const TICKER_KEY = 'ticker' +const WALLET_KEY = 'wallet' +const EXCHANGE_KEY = 'exchange' +const ZERO_CONF_KEY = 'zeroConf' +const EDIT_KEY = 'edit' +const ENABLE_KEY = 'enabled' +const SIZE_KEY = 'size' +const TEXT_ALIGN_KEY = 'textAlign' + +export { + CRYPTOCURRENCY_KEY, + TICKER_KEY, + WALLET_KEY, + EXCHANGE_KEY, + ZERO_CONF_KEY, + EDIT_KEY, + ENABLE_KEY, + SIZE_KEY, + TEXT_ALIGN_KEY +} diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js index 465bba12..3dc6c9e0 100644 --- a/new-lamassu-admin/src/routing/routes.js +++ b/new-lamassu-admin/src/routing/routes.js @@ -13,6 +13,7 @@ import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo' import ServerLogs from 'src/pages/ServerLogs' import Services from 'src/pages/Services/Services' import Transactions from 'src/pages/Transactions/Transactions' +import WalletSettings from 'src/pages/Wallet/WalletSettings' import MachineStatus from 'src/pages/maintenance/MachineStatus' const tree = [ @@ -93,6 +94,12 @@ const tree = [ label: 'Operator Info', route: '/settings/operator-info', component: OperatorInfo + }, + { + key: 'wallet', + label: 'Wallet', + route: '/settings/wallet', + component: WalletSettings } ] }, diff --git a/new-lamassu-admin/src/styling/logos/icon-bitcoin-colour.svg b/new-lamassu-admin/src/styling/logos/icon-bitcoin-colour.svg new file mode 100644 index 00000000..5b3af8c7 --- /dev/null +++ b/new-lamassu-admin/src/styling/logos/icon-bitcoin-colour.svg @@ -0,0 +1,4 @@ + + + + diff --git a/new-lamassu-admin/src/styling/logos/icon-bitcoincash-colour.svg b/new-lamassu-admin/src/styling/logos/icon-bitcoincash-colour.svg new file mode 100644 index 00000000..abc16f63 --- /dev/null +++ b/new-lamassu-admin/src/styling/logos/icon-bitcoincash-colour.svg @@ -0,0 +1,4 @@ + + + + diff --git a/new-lamassu-admin/src/styling/logos/icon-dash-colour.svg b/new-lamassu-admin/src/styling/logos/icon-dash-colour.svg new file mode 100644 index 00000000..73da05d9 --- /dev/null +++ b/new-lamassu-admin/src/styling/logos/icon-dash-colour.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/new-lamassu-admin/src/styling/logos/icon-ethereum-colour.svg b/new-lamassu-admin/src/styling/logos/icon-ethereum-colour.svg new file mode 100644 index 00000000..f095d8c0 --- /dev/null +++ b/new-lamassu-admin/src/styling/logos/icon-ethereum-colour.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/new-lamassu-admin/src/styling/logos/icon-litecoin-colour.svg b/new-lamassu-admin/src/styling/logos/icon-litecoin-colour.svg new file mode 100644 index 00000000..0e25829b --- /dev/null +++ b/new-lamassu-admin/src/styling/logos/icon-litecoin-colour.svg @@ -0,0 +1,4 @@ + + + + diff --git a/new-lamassu-admin/src/styling/logos/icon-zcash-colour.svg b/new-lamassu-admin/src/styling/logos/icon-zcash-colour.svg new file mode 100644 index 00000000..7e7655dc --- /dev/null +++ b/new-lamassu-admin/src/styling/logos/icon-zcash-colour.svg @@ -0,0 +1,15 @@ + + + + +headerArtboard 7 + + + +