feat: add wallet settings page

This commit is contained in:
Luis Félix 2020-02-07 12:43:30 +00:00 committed by Josh Harvey
parent 20674c4b12
commit 1f7ae74b42
31 changed files with 1793 additions and 258 deletions

View file

@ -1,6 +1,6 @@
import React from 'react'
import classnames from 'classnames'
import { makeStyles } from '@material-ui/core' 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 { ReactComponent as ErrorIcon } from 'src/styling/icons/warning-icon/tomato.svg'
import { errorColor } from 'src/styling/variables' import { errorColor } from 'src/styling/variables'
@ -20,8 +20,7 @@ const styles = {
alignItems: 'center', alignItems: 'center',
color: errorColor, color: errorColor,
margin: 0, margin: 0,
whiteSpace: 'break-spaces', whiteSpace: 'break-spaces'
width: 250
} }
} }

View file

@ -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 (
<MaterialModal onClose={handleClose} className={classes.modal} {...props}>
<Paper className={classnames(classes.modalContentWrapper, className)}>
<button
className={classnames(classes.iconButton, classes.closeIcon)}
onClick={() => handleClose()}>
<CloseIcon />
</button>
{children}
</Paper>
</MaterialModal>
)
}
export default Modal

View file

@ -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 (
<div className={classnames(className, classes.stages)}>
{R.range(1, currentStage).map(idx => (
<div key={idx} className={classes.wrapper}>
{idx > 1 && <div className={classnames(separatorClasses)} />}
<div className={classes.stage}>
{color === 'spring' && <CompleteStageIconSpring />}
{color === 'zodiac' && <CompleteStageIconZodiac />}
</div>
</div>
))}
<div className={classes.wrapper}>
{currentStage > 1 && <div className={classnames(separatorClasses)} />}
<div className={classes.stage}>
{color === 'spring' && <CurrentStageIconSpring />}
{color === 'zodiac' && <CurrentStageIconZodiac />}
</div>
</div>
{R.range(currentStage + 1, stages + 1).map(idx => (
<div key={idx} className={classes.wrapper}>
<div className={classnames(separatorEmptyClasses)} />
<div className={classes.stage}>
{color === 'spring' && <EmptyStageIconSpring />}
{color === 'zodiac' && <EmptyStageIconZodiac />}
</div>
</div>
))}
</div>
)
})
export default Stage

View file

@ -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 (
<Downshift
id={name}
itemToString={it => {
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
}) => (
<div className={classes.container}>
{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
})}
<Popper
open={isOpen}
anchorEl={popperNode}
modifiers={{ flip: { enabled: true } }}
style={{ zIndex: 9999 }}>
<div
{...(isOpen
? getMenuProps({}, { suppressRefError: true })
: {})}>
<Paper
square
style={{
minWidth: popperNode ? popperNode.clientWidth + 2 : null
}}>
{filterSuggestions(
suggestions,
inputValue2,
value ? R.of(value) : [],
code,
display
).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion }),
highlightedIndex,
selectedItem: selectedItem2,
code,
display
})
)}
</Paper>
</div>
</Popper>
</div>
)}
</Downshift>
)
}
)
export default withStyles(styles)(Autocomplete)

View file

@ -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 (
<Downshift
id={name}
itemToString={it => {
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
}) => (
<div className={classnames(classes.container, className)}>
{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
})}
<Popper
open={isOpen}
anchorEl={popperNode}
modifiers={{ flip: { enabled: true } }}
style={{ zIndex: 9999 }}>
<div
{...(isOpen
? getMenuProps({}, { suppressRefError: true })
: {})}>
<Paper
square
style={{
minWidth: popperNode ? popperNode.clientWidth + 2 : null
}}>
{filterSuggestions(
suggestions,
inputValue2,
value ? R.of(value) : [],
code,
display
).map((suggestion, index) =>
renderSuggestion({
suggestion,
index,
itemProps: getItemProps({ item: suggestion }),
highlightedIndex,
selectedItem: selectedItem2,
code,
display
})
)}
</Paper>
</div>
</Popper>
</div>
)}
</Downshift>
)
}
)
export default withStyles(styles)(AutocompleteSelect)

View file

@ -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 (
<TextInput
name={name}
onChange={onChange}
onBlur={onBlur}
value={value}
error={!!error}
InputProps={InputProps}
{...props}
/>
)
}
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 (
<StyledMenuItem
{...itemProps}
key={suggestion[code]}
selected={isHighlighted}
component="div">
{suggestion[display]}
</StyledMenuItem>
)
}
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 }

View file

@ -15,6 +15,7 @@ const { p } = typographyStyles
const GreenRadio = withStyles({ const GreenRadio = withStyles({
root: { root: {
color: secondaryColor, color: secondaryColor,
padding: [[9, 8, 9, 9]],
'&$checked': { '&$checked': {
color: secondaryColor color: secondaryColor
} }

View file

@ -1,13 +1,13 @@
import React, { memo, useState } from 'react'
import classnames from 'classnames'
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import classnames from 'classnames'
import React, { memo, useState } from 'react'
import TextInputFormik from './TextInput' import TextInputFormik from './TextInput'
import { styles } from './TextInput.styles' import { styles } from './TextInput.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const SecretInputFormik = memo(({ ...props }) => { const SecretInputFormik = memo(({ className, ...props }) => {
const { value } = props.field const { value } = props.field
const classes = useStyles() const classes = useStyles()
@ -37,7 +37,7 @@ const SecretInputFormik = memo(({ ...props }) => {
<TextInputFormik <TextInputFormik
{...props} {...props}
onFocus={handleFocus} onFocus={handleFocus}
className={classnames(inputClass)} className={classnames(inputClass, className)}
/> />
</> </>
) )

View file

@ -1,3 +1,4 @@
import AutocompleteSelect from './autocomplete/AutocompleteSelect'
import Checkbox from './base/Checkbox' import Checkbox from './base/Checkbox'
import Radio from './base/Radio' import Radio from './base/Radio'
import RadioGroup from './base/RadioGroup' import RadioGroup from './base/RadioGroup'
@ -5,4 +6,12 @@ import Select from './base/Select'
import Switch from './base/Switch' import Switch from './base/Switch'
import TextInput from './base/TextInput' import TextInput from './base/TextInput'
export { TextInput, Radio, Checkbox, Switch, Select, RadioGroup } export {
AutocompleteSelect,
TextInput,
Radio,
Checkbox,
Switch,
Select,
RadioGroup
}

View file

@ -7,5 +7,11 @@ export default {
}, },
titleAndButtonsContainer: { titleAndButtonsContainer: {
display: 'flex' display: 'flex'
},
iconButton: {
border: 'none',
outline: 0,
backgroundColor: 'transparent',
cursor: 'pointer'
} }
} }

View file

@ -81,10 +81,9 @@ const BitgoCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const BitgoForm = ({ account, handleSubmit, ...props }) => { const getBitgoFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const { code } = account
const token = getValue(schema.token.code) const token = getValue(schema.token.code)
const btcWalletId = getValue(schema.btcWalletId.code) const btcWalletId = getValue(schema.btcWalletId.code)
const btcWalletPassphrase = getValue(schema.btcWalletPassphrase.code) const btcWalletPassphrase = getValue(schema.btcWalletPassphrase.code)
@ -98,7 +97,7 @@ const BitgoForm = ({ account, handleSubmit, ...props }) => {
const dashWalletPassphrase = getValue(schema.dashWalletPassphrase.code) const dashWalletPassphrase = getValue(schema.dashWalletPassphrase.code)
const environment = getValue(schema.environment.code) const environment = getValue(schema.environment.code)
const formik = { return {
initialValues: { initialValues: {
token: token, token: token,
BTCWalletId: btcWalletId, BTCWalletId: btcWalletId,
@ -157,8 +156,9 @@ const BitgoForm = ({ account, handleSubmit, ...props }) => {
return errors return errors
} }
} }
}
const fields = [ const getBitgoFields = () => [
{ {
name: schema.token.code, name: schema.token.code,
label: schema.token.display, label: schema.token.display,
@ -234,6 +234,13 @@ const BitgoForm = ({ account, handleSubmit, ...props }) => {
} }
] ]
const BitgoForm = ({ account, handleSubmit, ...props }) => {
const { code } = account
const formik = getBitgoFormik(account)
const fields = getBitgoFields()
return ( return (
<> <>
<EditService <EditService
@ -247,4 +254,4 @@ const BitgoForm = ({ account, handleSubmit, ...props }) => {
) )
} }
export { BitgoForm, BitgoCard } export { BitgoForm, BitgoCard, getBitgoFormik, getBitgoFields }

View file

@ -52,15 +52,14 @@ const BitstampCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const BitstampForm = ({ account, ...props }) => { const getBitstampFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const { code } = account
const clientId = getValue(schema.clientId.code) const clientId = getValue(schema.clientId.code)
const key = getValue(schema.key.code) const key = getValue(schema.key.code)
const secret = getValue(schema.secret.code) const secret = getValue(schema.secret.code)
const formik = { return {
initialValues: { initialValues: {
clientId: clientId, clientId: clientId,
key: key, key: key,
@ -78,8 +77,9 @@ const BitstampForm = ({ account, ...props }) => {
.required('Required') .required('Required')
}) })
} }
}
const fields = [ const getBitstampFields = () => [
{ {
name: schema.clientId.code, name: schema.clientId.code,
label: schema.clientId.display, label: schema.clientId.display,
@ -100,6 +100,13 @@ const BitstampForm = ({ account, ...props }) => {
} }
] ]
const BitstampForm = ({ account, ...props }) => {
const { code } = account
const formik = getBitstampFormik(account)
const fields = getBitstampFields()
return ( return (
<> <>
<EditService <EditService
@ -113,4 +120,4 @@ const BitstampForm = ({ account, ...props }) => {
) )
} }
export { BitstampForm, BitstampCard } export { BitstampForm, BitstampCard, getBitstampFormik, getBitstampFields }

View file

@ -47,14 +47,13 @@ const BlockcypherCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const BlockcypherForm = ({ account, ...props }) => { const getBlockcypherFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const { code } = account
const token = getValue(schema.token.code) const token = getValue(schema.token.code)
const confidenceFactor = getValue(schema.confidenceFactor.code) const confidenceFactor = getValue(schema.confidenceFactor.code)
const formik = { return {
initialValues: { initialValues: {
token: token, token: token,
confidenceFactor: confidenceFactor confidenceFactor: confidenceFactor
@ -69,8 +68,9 @@ const BlockcypherForm = ({ account, ...props }) => {
.required('Required') .required('Required')
}) })
} }
}
const fields = [ const getBlockcypherFields = () => [
{ {
name: schema.token.code, name: schema.token.code,
label: schema.token.display, label: schema.token.display,
@ -85,6 +85,13 @@ const BlockcypherForm = ({ account, ...props }) => {
} }
] ]
const BlockcypherForm = ({ account, ...props }) => {
const { code } = account
const formik = getBlockcypherFormik(account)
const fields = getBlockcypherFields()
return ( return (
<> <>
<EditService <EditService
@ -98,4 +105,9 @@ const BlockcypherForm = ({ account, ...props }) => {
) )
} }
export { BlockcypherForm, BlockcypherCard } export {
BlockcypherForm,
BlockcypherCard,
getBlockcypherFormik,
getBlockcypherFields
}

View file

@ -52,15 +52,14 @@ const InfuraCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const InfuraForm = ({ account, ...props }) => { const getInfuraFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const { code } = account
const apiKey = getValue(schema.apiKey.code) const apiKey = getValue(schema.apiKey.code)
const apiSecret = getValue(schema.apiSecret.code) const apiSecret = getValue(schema.apiSecret.code)
const endpoint = getValue(schema.endpoint.code) const endpoint = getValue(schema.endpoint.code)
const formik = { return {
initialValues: { initialValues: {
apiKey: apiKey, apiKey: apiKey,
apiSecret: apiSecret, apiSecret: apiSecret,
@ -79,8 +78,10 @@ const InfuraForm = ({ account, ...props }) => {
.required('Required') .required('Required')
}) })
} }
}
const fields = [ const getInfuraFields = () => {
return [
{ {
name: schema.apiKey.code, name: schema.apiKey.code,
label: schema.apiKey.display, label: schema.apiKey.display,
@ -100,6 +101,14 @@ const InfuraForm = ({ account, ...props }) => {
component: TextInputFormik component: TextInputFormik
} }
] ]
}
const InfuraForm = ({ account, ...props }) => {
const { code } = account
const formik = getInfuraFormik(account)
const fields = getInfuraFields()
return ( return (
<> <>
@ -114,4 +123,4 @@ const InfuraForm = ({ account, ...props }) => {
) )
} }
export { InfuraCard, InfuraForm } export { InfuraCard, InfuraForm, getInfuraFormik, getInfuraFields }

View file

@ -51,16 +51,15 @@ const ItbitCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const ItbitForm = ({ account, ...props }) => { const getItbitFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const { code } = account
const userId = getValue(schema.userId.code) const userId = getValue(schema.userId.code)
const walletId = getValue(schema.walletId.code) const walletId = getValue(schema.walletId.code)
const clientKey = getValue(schema.clientKey.code) const clientKey = getValue(schema.clientKey.code)
const clientSecret = getValue(schema.clientSecret.code) const clientSecret = getValue(schema.clientSecret.code)
const formik = { return {
initialValues: { initialValues: {
userId: userId, userId: userId,
walletId: walletId, walletId: walletId,
@ -82,8 +81,9 @@ const ItbitForm = ({ account, ...props }) => {
.required('Required') .required('Required')
}) })
} }
}
const fields = [ const getItbitFields = () => [
{ {
name: schema.userId.code, name: schema.userId.code,
label: schema.userId.display, label: schema.userId.display,
@ -110,6 +110,13 @@ const ItbitForm = ({ account, ...props }) => {
} }
] ]
const ItbitForm = ({ account, ...props }) => {
const { code } = account
const formik = getItbitFormik(account)
const fields = getItbitFields()
return ( return (
<> <>
<EditService <EditService
@ -123,4 +130,4 @@ const ItbitForm = ({ account, ...props }) => {
) )
} }
export { ItbitCard, ItbitForm } export { ItbitCard, ItbitForm, getItbitFormik, getItbitFields }

View file

@ -48,14 +48,13 @@ const KrakenCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const KrakenForm = ({ account, ...props }) => { const getKrakenFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const { code } = account
const apiKey = getValue(schema.apiKey.code) const apiKey = getValue(schema.apiKey.code)
const privateKey = getValue(schema.privateKey.code) const privateKey = getValue(schema.privateKey.code)
const formik = { return {
initialValues: { initialValues: {
apiKey: apiKey, apiKey: apiKey,
privateKey: privateKey privateKey: privateKey
@ -69,8 +68,9 @@ const KrakenForm = ({ account, ...props }) => {
.required('Required') .required('Required')
}) })
} }
}
const fields = [ const getKrakenFields = () => [
{ {
name: schema.apiKey.code, name: schema.apiKey.code,
label: schema.apiKey.display, label: schema.apiKey.display,
@ -85,6 +85,13 @@ const KrakenForm = ({ account, ...props }) => {
} }
] ]
const KrakenForm = ({ account, ...props }) => {
const { code } = account
const formik = getKrakenFormik(account)
const fields = getKrakenFields()
return ( return (
<> <>
<EditService <EditService
@ -98,4 +105,4 @@ const KrakenForm = ({ account, ...props }) => {
) )
} }
export { KrakenCard, KrakenForm } export { KrakenCard, KrakenForm, getKrakenFormik, getKrakenFields }

View file

@ -55,16 +55,15 @@ const MailgunCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const MailgunForm = ({ account, ...props }) => { const getMailgunFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const { code } = account
const apiKey = getValue(schema.apiKey.code) const apiKey = getValue(schema.apiKey.code)
const domain = getValue(schema.domain.code) const domain = getValue(schema.domain.code)
const fromEmail = getValue(schema.fromEmail.code) const fromEmail = getValue(schema.fromEmail.code)
const toEmail = getValue(schema.toEmail.code) const toEmail = getValue(schema.toEmail.code)
const formik = { return {
initialValues: { initialValues: {
apiKey: apiKey, apiKey: apiKey,
domain: domain, domain: domain,
@ -88,8 +87,9 @@ const MailgunForm = ({ account, ...props }) => {
.required('Required') .required('Required')
}) })
} }
}
const fields = [ const getMailgunFields = () => [
{ {
name: schema.apiKey.code, name: schema.apiKey.code,
label: schema.apiKey.display, label: schema.apiKey.display,
@ -116,6 +116,13 @@ const MailgunForm = ({ account, ...props }) => {
} }
] ]
const MailgunForm = ({ account, ...props }) => {
const { code } = account
const formik = getMailgunFormik(account)
const fields = getMailgunFields()
return ( return (
<> <>
<EditService <EditService
@ -129,4 +136,4 @@ const MailgunForm = ({ account, ...props }) => {
) )
} }
export { MailgunCard, MailgunForm } export { MailgunCard, MailgunForm, getMailgunFormik, getMailgunFields }

View file

@ -37,13 +37,12 @@ const StrikeCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const StrikeForm = ({ account, ...props }) => { const getStrikeFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const code = 'strike'
const token = getValue(schema.token.code) const token = getValue(schema.token.code)
const formik = { return {
initialValues: { initialValues: {
token: token token: token
}, },
@ -53,8 +52,9 @@ const StrikeForm = ({ account, ...props }) => {
.required('Required') .required('Required')
}) })
} }
}
const fields = [ const getStrikeFields = () => [
{ {
name: schema.token.code, name: schema.token.code,
label: schema.token.display, label: schema.token.display,
@ -63,6 +63,13 @@ const StrikeForm = ({ account, ...props }) => {
} }
] ]
const StrikeForm = ({ account, ...props }) => {
const code = 'strike'
const formik = getStrikeFormik(account)
const fields = getStrikeFields()
return ( return (
<> <>
<EditService <EditService
@ -76,4 +83,4 @@ const StrikeForm = ({ account, ...props }) => {
) )
} }
export { StrikeCard, StrikeForm } export { StrikeCard, StrikeForm, getStrikeFormik, getStrikeFields }

View file

@ -56,16 +56,15 @@ const TwilioCard = memo(({ account, onEdit, ...props }) => {
) )
}) })
const TwilioForm = ({ account, ...props }) => { const getTwilioFormik = account => {
const getValue = getValueAux(account) const getValue = getValueAux(account)
const { code } = account
const accountSid = getValue(schema.accountSid.code) const accountSid = getValue(schema.accountSid.code)
const authToken = getValue(schema.authToken.code) const authToken = getValue(schema.authToken.code)
const fromNumber = getValue(schema.fromNumber.code) const fromNumber = getValue(schema.fromNumber.code)
const toNumber = getValue(schema.toNumber.code) const toNumber = getValue(schema.toNumber.code)
const formik = { return {
initialValues: { initialValues: {
accountSid: accountSid, accountSid: accountSid,
authToken: authToken, authToken: authToken,
@ -87,8 +86,9 @@ const TwilioForm = ({ account, ...props }) => {
.required('Required') .required('Required')
}) })
} }
}
const fields = [ const getTwilioFields = () => [
{ {
name: schema.accountSid.code, name: schema.accountSid.code,
label: schema.accountSid.display, label: schema.accountSid.display,
@ -115,6 +115,13 @@ const TwilioForm = ({ account, ...props }) => {
} }
] ]
const TwilioForm = ({ account, ...props }) => {
const { code } = account
const formik = getTwilioFormik(account)
const fields = getTwilioFields()
return ( return (
<> <>
<EditService <EditService
@ -128,4 +135,4 @@ const TwilioForm = ({ account, ...props }) => {
) )
} }
export { TwilioCard, TwilioForm } export { TwilioCard, TwilioForm, getTwilioFormik, getTwilioFields }

View file

@ -1,10 +1,10 @@
import React from 'react'
import * as R from 'ramda'
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import * as R from 'ramda'
import React from 'react'
import SingleRowTable from 'src/components/single-row-table/SingleRowTable' 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 => { const formatLong = value => {
if (!value) return '' if (!value) return ''

View file

@ -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(
<WizardSplash
code={row[CRYPTOCURRENCY_KEY]}
coinName={getCryptoDisplayName(row)}
handleModalNavigation={handleModalNavigation(row)}
/>
)
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(
<Wizard
crypto={row}
coinName={getCryptoDisplayName(row)}
handleModalNavigation={handleModalNavigation}
pageName={TICKER_KEY}
currentStage={1}
alreadySetUp={R.filter(
ticker => R.includes(cryptocode, ticker.cryptos),
R.filter(R.propEq('class', 'ticker'), accounts)
)}
/>
)
break
case 2:
setModalContent(
<Wizard
crypto={row}
coinName={getCryptoDisplayName(row)}
handleModalNavigation={handleModalNavigation}
pageName={WALLET_KEY}
currentStage={2}
alreadySetUp={getAlreadySetUp(WALLET_KEY)(cryptocode)}
notSetUp={getNotSetUp(WALLET_KEY)(cryptocode)}
saveNewService={saveNewService}
/>
)
break
case 3:
setModalContent(
<Wizard
crypto={row}
coinName={getCryptoDisplayName(row)}
handleModalNavigation={handleModalNavigation}
pageName={EXCHANGE_KEY}
currentStage={3}
alreadySetUp={getAlreadySetUp(EXCHANGE_KEY)(cryptocode)}
notSetUp={getNotSetUp(EXCHANGE_KEY)(cryptocode)}
saveNewService={saveNewService}
/>
)
break
case 4:
setModalContent(
<Wizard
crypto={row}
coinName={getCryptoDisplayName(row)}
handleModalNavigation={handleModalNavigation}
pageName={ZERO_CONF_KEY}
currentStage={4}
alreadySetUp={getAlreadySetUp(ZERO_CONF_KEY)(cryptocode)}
notSetUp={getNotSetUp(ZERO_CONF_KEY)(cryptocode)}
saveNewService={saveNewService}
/>
)
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 (
<>
<div className={classes.titleWrapper}>
<div className={classes.titleAndButtonsContainer}>
<Title>Wallet Settings</Title>
{error && !modalOpen && (
<ErrorMessage className={classes.switchErrorMessage}>
Failed to save
</ErrorMessage>
)}
</div>
</div>
<div className={classes.wrapper}>
<Table>
<THead>
<Th
size={getSize(CRYPTOCURRENCY_KEY)}
textAlign={getTextAlign(CRYPTOCURRENCY_KEY)}>
Cryptocurrency
</Th>
<Th size={getSize(TICKER_KEY)} textAlign={getTextAlign(TICKER_KEY)}>
Ticker
</Th>
<Th size={getSize(WALLET_KEY)} textAlign={getTextAlign(WALLET_KEY)}>
Wallet
</Th>
<Th
size={getSize(EXCHANGE_KEY)}
textAlign={getTextAlign(EXCHANGE_KEY)}>
Exchange
</Th>
<Th
size={getSize(ZERO_CONF_KEY)}
textAlign={getTextAlign(ZERO_CONF_KEY)}>
Zero Conf
</Th>
<Th size={getSize(EDIT_KEY)} textAlign={getTextAlign(EDIT_KEY)}>
Edit
</Th>
<Th size={getSize(ENABLE_KEY)} textAlign={getTextAlign(ENABLE_KEY)}>
Enable
</Th>
</THead>
<TBody>
{state.map((row, idx) => (
<Tr key={idx}>
<Td
size={getSize(CRYPTOCURRENCY_KEY)}
textAlign={getTextAlign(CRYPTOCURRENCY_KEY)}>
{getCryptoDisplayName(row)}
</Td>
{!isSet(row) && (
<Td
size={
getSize(TICKER_KEY) +
getSize(WALLET_KEY) +
getSize(EXCHANGE_KEY) +
getSize(ZERO_CONF_KEY)
}
textAlign="center"
className={classes.disabledDrawing}>
<div />
</Td>
)}
{isSet(row) && (
<>
<Td
size={getSize(TICKER_KEY)}
textAlign={getTextAlign(TICKER_KEY)}>
{getDisplayName(accounts)(row[TICKER_KEY])}
</Td>
<Td
size={getSize(WALLET_KEY)}
textAlign={getTextAlign(WALLET_KEY)}>
{getDisplayName(accounts)(row[WALLET_KEY])}
</Td>
<Td
size={getSize(EXCHANGE_KEY)}
textAlign={getTextAlign(EXCHANGE_KEY)}>
{getDisplayName(accounts)(row[EXCHANGE_KEY])}
</Td>
<Td
size={getSize(ZERO_CONF_KEY)}
textAlign={getTextAlign(ZERO_CONF_KEY)}>
{getDisplayName(accounts)(row[ZERO_CONF_KEY])}
</Td>
</>
)}
<Td size={getSize(EDIT_KEY)} textAlign={getTextAlign(EDIT_KEY)}>
{!isSet(row) && <DisabledEditIcon />}
{isSet(row) && (
<button
className={classes.iconButton}
onClick={() => handleEditClick(row)}>
<EditIcon />
</button>
)}
</Td>
<Td
size={getSize(ENABLE_KEY)}
textAlign={getTextAlign(ENABLE_KEY)}>
<Switch
checked={row[ENABLE_KEY]}
onChange={handleEnable(row)}
value={row[CRYPTOCURRENCY_KEY]}
/>
</Td>
</Tr>
))}
</TBody>
</Table>
</div>
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={modalOpen}
handleClose={handleModalClose}
className={classes.modal}>
{modalContent}
</Modal>
</>
)
}
export default WalletSettings

View file

@ -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 (
<div className={classes.submitButtonWrapper}>
{error && <ErrorMessage>Failed to save</ErrorMessage>}
<Button {...props}>Next</Button>
</div>
)
}
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 (
<div className={classes.modalContent}>
<H1>Enable {coinName}</H1>
<Info2>{startCase(pageName)}</Info2>
<Stage
stages={4}
currentStage={currentStage}
color="spring"
className={classes.stages}
/>
<H4>{`Select a ${pageName} or set up a new one`}</H4>
<div className={classnames(radiosClassNames)}>
{alreadySetUp && (
<RadioGroup
name="already-setup-select"
value={selectedRadio || radioButtonOptions[0]}
options={radioButtonOptions}
ariaLabel="already-setup-select"
onChange={handleRadioButtons}
className={classes.alreadySetupRadioButtons}
/>
)}
{notSetUp && (
<div className={classes.selectNewWrapper}>
<RadioGroup
name="setup-new-select"
value={setUpNew || ''}
options={[{ label: 'Set up new', value: 'new' }]}
ariaLabel="setup-new-select"
onChange={handleSetUpNew}
className={classes.alreadySetupRadioButtons}
/>
{setUpNew && (
<AutocompleteSelect
id="chooseNew"
name="chooseNew"
label={`Select ${pageName}`}
suggestions={notSetUp}
value={selectedFromDropdown}
handleChange={handleSelectFromDropdown}
className={classes.selectNew}
/>
)}
</div>
)}
</div>
{formContent && (
<Formik
initialValues={formContent.formik.initialValues}
validationSchema={formContent.formik.validationSchema}
onSubmit={values =>
saveNewService(selectedFromDropdown.code, values)
.then(m => {
handleNext(selectedFromDropdown.code)()
})
.catch(error => setError(error))
}>
{props => (
<form
onReset={props.handleReset}
onSubmit={props.handleSubmit}
className={classes.newServiceForm}
{...props}>
<div className={classes.newServiceFormFields}>
{formContent.fields.map((field, idx) => (
<div key={idx} className={classes.field}>
<FormikField
id={field.name}
name={field.name}
component={field.component}
placeholder={field.placeholder}
type={field.type}
label={field.label}
className={classes.formInput}
onFocus={() => {
setError(null)
}}
/>
</div>
))}
</div>
<SubmitButton
disabled={R.isEmpty(props.touched) || !props.isValid}
className={classes.submitButton}
type="submit"
error={error}
/>
</form>
)}
</Formik>
)}
{!formContent && (
<SubmitButton
className={classes.submitButton}
disabled={!isSubmittable()}
onClick={handleNext(selectedRadio || selectedFromDropdown?.code)}
error={error}
/>
)}
</div>
)
}
export default Wizard

View file

@ -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 <BitcoinLogo />
case 'BCH':
return <BitcoinCashLogo />
case 'DASH':
return <DashLogo />
case 'ETH':
return <EthereumLogo />
case 'LTC':
return <LitecoinLogo />
case 'ZEC':
return <ZCashLogo />
default:
return null
}
}
const WizardSplash = ({ code, coinName, handleModalNavigation }) => {
const classes = useStyles()
return (
<div className={classes.modalContent}>
<div className={classes.logoWrapper}>{renderLogo(code)}</div>
<H1>Enable {coinName}</H1>
<P>
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, youll
have to setup all the necessary 3rd party services.
</P>
<Button onClick={() => handleModalNavigation(1)}>
Start configuration
</Button>
</div>
)
}
export default WizardSplash

View file

@ -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
}

View file

@ -13,6 +13,7 @@ import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo'
import ServerLogs from 'src/pages/ServerLogs' import ServerLogs from 'src/pages/ServerLogs'
import Services from 'src/pages/Services/Services' import Services from 'src/pages/Services/Services'
import Transactions from 'src/pages/Transactions/Transactions' import Transactions from 'src/pages/Transactions/Transactions'
import WalletSettings from 'src/pages/Wallet/WalletSettings'
import MachineStatus from 'src/pages/maintenance/MachineStatus' import MachineStatus from 'src/pages/maintenance/MachineStatus'
const tree = [ const tree = [
@ -93,6 +94,12 @@ const tree = [
label: 'Operator Info', label: 'Operator Info',
route: '/settings/operator-info', route: '/settings/operator-info',
component: OperatorInfo component: OperatorInfo
},
{
key: 'wallet',
label: 'Wallet',
route: '/settings/wallet',
component: WalletSettings
} }
] ]
}, },

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="64" version="1.1">
<circle fill="#F7931A" cx="52" cy="32" r="32"/>
<path fill="#FFF" d="m66.103,27.444c0.637-4.258-2.605-6.547-7.038-8.074l1.438-5.768-3.511-0.875-1.4,5.616c-0.923-0.23-1.871-0.447-2.813-0.662l1.41-5.653-3.509-0.875-1.439,5.766c-0.764-0.174-1.514-0.346-2.242-0.527l0.004-0.018-4.842-1.209-0.934,3.75s2.605,0.597,2.55,0.634c1.422,0.355,1.679,1.296,1.636,2.042l-1.638,6.571c0.098,0.025,0.225,0.061,0.365,0.117-0.117-0.029-0.242-0.061-0.371-0.092l-2.296,9.205c-0.174,0.432-0.615,1.08-1.609,0.834,0.035,0.051-2.552-0.637-2.552-0.637l-1.743,4.019,4.569,1.139c0.85,0.213,1.683,0.436,2.503,0.646l-1.453,5.834,3.507,0.875,1.439-5.772c0.958,0.26,1.888,0.5,2.798,0.726l-1.434,5.745,3.511,0.875,1.453-5.823c5.987,1.133,10.489,0.676,12.384-4.739,1.527-4.36-0.076-6.875-3.226-8.515,2.294-0.529,4.022-2.038,4.483-5.155zm-8.022,11.249c-1.085,4.36-8.426,2.003-10.806,1.412l1.928-7.729c2.38,0.594,10.012,1.77,8.878,6.317zm1.086-11.312c-0.99,3.966-7.1,1.951-9.082,1.457l1.748-7.01c1.982,0.494,8.365,1.416,7.334,5.553z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="64" version="1.1">
<path fill="#F7931A" d="m0,0l29.7,0a39,39,0,0,0,0,64l-29.7,0zm52,0a32,32,0,0,0,0,64a32,32,0,0,0,0,-64m52,0l-29.7,0a39,39,0,0,1,0,64l29.7,0z"/>
<path fill="#FFF" transform="rotate(-28 52 32)" d="m66.103,27.444c0.637-4.258-2.605-6.547-7.038-8.074l1.438-5.768-3.511-0.875-1.4,5.616c-0.923-0.23-1.871-0.447-2.813-0.662l1.41-5.653-3.509-0.875-1.439,5.766c-0.764-0.174-1.514-0.346-2.242-0.527l0.004-0.018-4.842-1.209-0.934,3.75s2.605,0.597,2.55,0.634c1.422,0.355,1.679,1.296,1.636,2.042l-1.638,6.571c0.098,0.025,0.225,0.061,0.365,0.117-0.117-0.029-0.242-0.061-0.371-0.092l-2.296,9.205c-0.174,0.432-0.615,1.08-1.609,0.834,0.035,0.051-2.552-0.637-2.552-0.637l-1.743,4.019,4.569,1.139c0.85,0.213,1.683,0.436,2.503,0.646l-1.453,5.834,3.507,0.875,1.439-5.772c0.958,0.26,1.888,0.5,2.798,0.726l-1.434,5.745,3.511,0.875,1.453-5.823c5.987,1.133,10.489,0.676,12.384-4.739,1.527-4.36-0.076-6.875-3.226-8.515,2.294-0.529,4.022-2.038,4.483-5.155zm-8.022,11.249c-1.085,4.36-8.426,2.003-10.806,1.412l1.928-7.729c2.38,0.594,10.012,1.77,8.878,6.317zm1.086-11.312c-0.99,3.966-7.1,1.951-9.082,1.457l1.748-7.01c1.982,0.494,8.365,1.416,7.334,5.553z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 2000" width="2500" height="2500"><circle cx="1000" cy="1000" r="1000" fill="#2573c2"/><g fill="#fff"><path d="M1652.6 736.8a143.65 143.65 0 0 0-19.2-63.6c-10-20-27.8-35.6-48.6-43.6a143.51 143.51 0 0 0-68.4-15H628.8l-63.6 190.6h804.2l-127 389.6h-804l-63.6 190.6h891.8a246.33 246.33 0 0 0 77.8-15c25-14.2 53.6-28.6 77.8-48.6a382.69 382.69 0 0 0 63.6-63.6 432.2 432.2 0 0 0 39.2-73.4l117.8-370.4a137.38 137.38 0 0 0 9.8-77.6z"/><path d="M882.2 911.6H409l-63.6 176.2h478z"/></g></svg>

After

Width:  |  Height:  |  Size: 538 B

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"></polygon>
<polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"></polygon>
<polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"></polygon>
<polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"></polygon>
<polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"></polygon>
<polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"></polygon>
</g>
</svg>

After

Width:  |  Height:  |  Size: 895 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="104" height="64" version="1.1">
<circle fill="#989898" cx="52" cy="32" r="32"/>
<path fill="#FFF" d="m46.92598,12.31703l-4.97801,18.8088l-3.42705,1.32711l-1.64691,6.17189l3.42705,-1.29514l-2.8461,10.76083l30.60893,0l2.09993,-7.98401l-18.42505,0l2.09993,-7.82412l3.3631,-1.26316l1.6469,-6.17188l-3.36309,1.29513l3.6509,-13.83611l-12.21053,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 398 B

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 493.3 490.2" style="enable-background:new 0 0 493.3 490.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#231F20;}
.st1{fill:#F4B728;}
</style>
<title>headerArtboard 7</title>
<path class="st0" d="M245.4,20C121.1,20,20,121.1,20,245.4s101.1,225.4,225.4,225.4s225.4-101.1,225.4-225.4S369.7,20,245.4,20z
M245.4,433.6c-103.8,0-188.2-84.4-188.2-188.2S141.6,57.2,245.4,57.2s188.2,84.4,188.2,188.2S349.2,433.6,245.4,433.6z"/>
<circle class="st1" cx="245.4" cy="245.4" r="177.6"/>
<polygon class="st0" points="165,315.5 165,349.9 226.5,349.9 226.5,387.6 264.3,387.6 264.3,349.9 325.8,349.9 325.8,304.4
230.4,304.4 325.8,175 325.8,140.6 264.3,140.6 264.3,103 226.5,103 226.5,140.6 165,140.6 165,186.2 260.4,186.2 "/>
</svg>

After

Width:  |  Height:  |  Size: 1,007 B