Merge pull request #1017 from chaotixkilla/fix-ui-ux-tweaks

Improve UI/UX on multiple screens
This commit is contained in:
Rafael Taranto 2022-01-20 12:31:49 +00:00 committed by GitHub
commit 412f3ac2b5
25 changed files with 246 additions and 153 deletions

View file

@ -1,7 +1,7 @@
import { useLazyQuery } from '@apollo/react-hooks' import { useLazyQuery } from '@apollo/react-hooks'
import { makeStyles, ClickAwayListener } from '@material-ui/core' import { makeStyles, ClickAwayListener } from '@material-ui/core'
import classnames from 'classnames' import classnames from 'classnames'
import { format, isSameDay } from 'date-fns/fp' import { format } from 'date-fns/fp'
import FileSaver from 'file-saver' import FileSaver from 'file-saver'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState, useCallback } from 'react' import React, { useState, useCallback } from 'react'
@ -189,7 +189,6 @@ const LogsDownloaderPopover = ({
if (!range || !range.from) return if (!range || !range.from) return
if (range.from && !range.until) range.until = new Date() if (range.from && !range.until) range.until = new Date()
if (isSameDay(range.until, range.from)) range.until = new Date()
if (selectedRadio === RANGE) { if (selectedRadio === RANGE) {
fetchLogs({ fetchLogs({

View file

@ -1,4 +1,5 @@
import { makeStyles, ClickAwayListener } from '@material-ui/core' import { makeStyles, ClickAwayListener } from '@material-ui/core'
import * as R from 'ramda'
import React, { useState, memo } from 'react' import React, { useState, memo } from 'react'
import Popper from 'src/components/Popper' import Popper from 'src/components/Popper'
@ -8,9 +9,9 @@ const useStyles = makeStyles({
transparentButton: { transparentButton: {
border: 'none', border: 'none',
backgroundColor: 'transparent', backgroundColor: 'transparent',
marginTop: 4,
outline: 'none', outline: 'none',
cursor: 'pointer' cursor: 'pointer',
marginTop: 4
}, },
popoverContent: ({ width }) => ({ popoverContent: ({ width }) => ({
width, width,
@ -69,11 +70,22 @@ const HoverableTooltip = memo(({ parentElements, children, width }) => {
return ( return (
<div> <div>
<div {!R.isNil(parentElements) && (
onMouseEnter={handler.handleOpenHelpPopper} <div
onMouseLeave={handler.handleCloseHelpPopper}> onMouseEnter={handler.handleOpenHelpPopper}
{parentElements} onMouseLeave={handler.handleCloseHelpPopper}>
</div> {parentElements}
</div>
)}
{R.isNil(parentElements) && (
<button
type="button"
onMouseEnter={handler.handleOpenHelpPopper}
onMouseLeave={handler.handleCloseHelpPopper}
className={handler.classes.transparentButton}>
<HelpIcon />
</button>
)}
<Popper <Popper
open={handler.helpPopperOpen} open={handler.helpPopperOpen}
anchorEl={handler.helpPopperAnchorEl} anchorEl={handler.helpPopperAnchorEl}

View file

@ -1,3 +1,5 @@
import { makeStyles } from '@material-ui/core'
import classnames from 'classnames'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useContext } from 'react' import React, { useContext } from 'react'
@ -11,6 +13,14 @@ import { startCase } from 'src/utils/string'
import TableCtx from './Context' import TableCtx from './Context'
const styles = {
orderedBySpan: {
whiteSpace: 'nowrap'
}
}
const useStyles = makeStyles(styles)
const groupSecondHeader = elements => { const groupSecondHeader = elements => {
const [toSHeader, noSHeader] = R.partition(R.has('doubleHeader'))(elements) const [toSHeader, noSHeader] = R.partition(R.has('doubleHeader'))(elements)
@ -31,6 +41,7 @@ const groupSecondHeader = elements => {
} }
const Header = () => { const Header = () => {
const classes = useStyles()
const { const {
elements, elements,
enableEdit, enableEdit,
@ -40,6 +51,7 @@ const Header = () => {
deleteWidth, deleteWidth,
enableToggle, enableToggle,
toggleWidth, toggleWidth,
orderedBy,
DEFAULT_COL_SIZE DEFAULT_COL_SIZE
} = useContext(TableCtx) } = useContext(TableCtx)
@ -60,11 +72,40 @@ const Header = () => {
const mapElement = ( const mapElement = (
{ name, width = DEFAULT_COL_SIZE, header, textAlign }, { name, width = DEFAULT_COL_SIZE, header, textAlign },
idx idx
) => ( ) => {
<Td header key={idx} width={width} textAlign={textAlign}> const orderClasses = classnames({
{header || startCase(name)} [classes.orderedBySpan]:
</Td> R.isNil(header) && !R.isNil(orderedBy) && R.equals(name, orderedBy.code)
) })
const attachOrderedByToComplexHeader = header => {
if (!R.isNil(orderedBy) && R.equals(name, orderedBy.code)) {
try {
const cloneHeader = R.clone(header)
const children = R.path(['props', 'children'], cloneHeader)
const spanChild = R.find(it => R.equals(it.type, 'span'), children)
spanChild.props.children = R.append(' -', spanChild.props.children)
return cloneHeader
} catch (e) {
return header
}
}
return header
}
return (
<Td header key={idx} width={width} textAlign={textAlign}>
{!R.isNil(header) ? (
<>{attachOrderedByToComplexHeader(header) ?? header}</>
) : (
<span className={orderClasses}>
{startCase(name)}{' '}
{!R.isNil(orderedBy) && R.equals(name, orderedBy.code) && '-'}
</span>
)}
</Td>
)
}
const [innerElements, HeaderElement] = groupSecondHeader(elements) const [innerElements, HeaderElement] = groupSecondHeader(elements)

View file

@ -56,7 +56,8 @@ const ETable = ({
sortBy, sortBy,
createText = 'Add override', createText = 'Add override',
forceAdd = false, forceAdd = false,
tbodyWrapperClass tbodyWrapperClass,
orderedBy = null
}) => { }) => {
const [editingId, setEditingId] = useState(null) const [editingId, setEditingId] = useState(null)
const [adding, setAdding] = useState(false) const [adding, setAdding] = useState(false)
@ -155,6 +156,7 @@ const ETable = ({
actionColSize, actionColSize,
stripeWhen, stripeWhen,
forceAdd, forceAdd,
orderedBy,
DEFAULT_COL_SIZE DEFAULT_COL_SIZE
} }

View file

@ -1,6 +1,7 @@
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import classnames from 'classnames' import classnames from 'classnames'
import { useSelect } from 'downshift' import { useSelect } from 'downshift'
import * as R from 'ramda'
import React from 'react' import React from 'react'
import { ReactComponent as Arrowdown } from 'src/styling/icons/action/arrow/regular.svg' import { ReactComponent as Arrowdown } from 'src/styling/icons/action/arrow/regular.svg'
@ -29,7 +30,9 @@ function Select({ className, label, items, ...props }) {
const selectClassNames = { const selectClassNames = {
[classes.select]: true, [classes.select]: true,
[classes.selectFiltered]: selectedItem !== props.default, [classes.selectFiltered]: props.defaultAsFilter
? true
: !R.equals(selectedItem, props.default),
[classes.open]: isOpen [classes.open]: isOpen
} }

View file

@ -27,16 +27,17 @@ const Sidebar = ({
{loading && <P>Loading...</P>} {loading && <P>Loading...</P>}
{!loading && {!loading &&
data?.map((it, idx) => ( data?.map((it, idx) => (
<div <div className={classes.linkWrapper} onClick={() => onClick(it)}>
key={idx} <div
className={classnames({ key={idx}
[classes.activeLink]: isSelected(it), className={classnames({
[classes.customRenderActiveLink]: itemRender && isSelected(it), [classes.activeLink]: isSelected(it),
[classes.customRenderLink]: itemRender, [classes.customRenderActiveLink]: itemRender && isSelected(it),
[classes.link]: true [classes.customRenderLink]: itemRender,
})} [classes.link]: true
onClick={() => onClick(it)}> })}>
{itemRender ? itemRender(it, isSelected(it)) : displayName(it)} {itemRender ? itemRender(it, isSelected(it)) : displayName(it)}
</div>
</div> </div>
))} ))}
{!loading && children} {!loading && children}

View file

@ -30,7 +30,9 @@ export default {
boxShadow: `-200px 0px 0px 0px ${sidebarColor}` boxShadow: `-200px 0px 0px 0px ${sidebarColor}`
} }
}, },
linkWrapper: {
cursor: 'pointer'
},
link: { link: {
extend: p, extend: p,
position: 'relative', position: 'relative',

View file

@ -19,6 +19,7 @@ export default {
}, },
buttonText: { buttonText: {
color: backgroundColor, color: backgroundColor,
fontFamily: 'Mont',
fontSize: 15 fontSize: 15
}, },
icon: { icon: {

View file

@ -5,7 +5,7 @@ import * as R from 'ramda'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import AppContext from 'src/AppContext' import AppContext from 'src/AppContext'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import TitleSection from 'src/components/layout/TitleSection' import TitleSection from 'src/components/layout/TitleSection'
import DataTable from 'src/components/tables/DataTable' import DataTable from 'src/components/tables/DataTable'
import { H4, Info2, P } from 'src/components/typography' import { H4, Info2, P } from 'src/components/typography'
@ -127,9 +127,9 @@ const Accounting = () => {
<span className={classes.operation}> <span className={classes.operation}>
{it.description} {it.description}
{!!it.extraInfo && ( {!!it.extraInfo && (
<Tooltip width={175}> <HoverableTooltip width={175}>
<P>{it.extraInfo}</P> <P>{it.extraInfo}</P>
</Tooltip> </HoverableTooltip>
)} )}
</span> </span>
) )

View file

@ -300,6 +300,7 @@ const Analytics = () => {
items={REPRESENTING_OPTIONS} items={REPRESENTING_OPTIONS}
default={REPRESENTING_OPTIONS[0]} default={REPRESENTING_OPTIONS[0]}
selectedItem={representing} selectedItem={representing}
defaultAsFilter
/> />
<Select <Select
label="Time period" label="Time period"
@ -307,6 +308,7 @@ const Analytics = () => {
items={PERIOD_OPTIONS} items={PERIOD_OPTIONS}
default={PERIOD_OPTIONS[0]} default={PERIOD_OPTIONS[0]}
selectedItem={period} selectedItem={period}
defaultAsFilter
/> />
</div> </div>
<div className={classes.overview}> <div className={classes.overview}>

View file

@ -7,7 +7,7 @@ import { utils as coinUtils } from 'lamassu-coins'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { Link } from 'src/components/buttons' import { Link } from 'src/components/buttons'
import { Switch } from 'src/components/inputs' import { Switch } from 'src/components/inputs'
import Sidebar from 'src/components/layout/Sidebar' import Sidebar from 'src/components/layout/Sidebar'
@ -186,13 +186,13 @@ const Blacklist = () => {
value={rejectAddressReuse} value={rejectAddressReuse}
/> />
<Label2>{rejectAddressReuse ? 'On' : 'Off'}</Label2> <Label2>{rejectAddressReuse ? 'On' : 'Off'}</Label2>
<Tooltip width={304}> <HoverableTooltip width={304}>
<P> <P>
The "Reject reused addresses" option means that all addresses The "Reject reused addresses" option means that all addresses
that are used once will be automatically rejected if there's that are used once will be automatically rejected if there's
an attempt to use them again on a new transaction. an attempt to use them again on a new transaction.
</P> </P>
</Tooltip> </HoverableTooltip>
</Box> </Box>
</Box> </Box>
<BlacklistTable <BlacklistTable

View file

@ -4,7 +4,7 @@ import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { NamespacedTable as EditableTable } from 'src/components/editableTable' import { NamespacedTable as EditableTable } from 'src/components/editableTable'
import { Switch } from 'src/components/inputs' import { Switch } from 'src/components/inputs'
import TitleSection from 'src/components/layout/TitleSection' import TitleSection from 'src/components/layout/TitleSection'
@ -54,7 +54,7 @@ const GET_INFO = gql`
const CashOut = ({ name: SCREEN_KEY }) => { const CashOut = ({ name: SCREEN_KEY }) => {
const classes = useStyles() const classes = useStyles()
const [wizard, setWizard] = useState(false) const [wizard, setWizard] = useState(false)
const { data } = useQuery(GET_INFO) const { data, loading } = useQuery(GET_INFO)
const [saveConfig, { error }] = useMutation(SAVE_CONFIG, { const [saveConfig, { error }] = useMutation(SAVE_CONFIG, {
onCompleted: () => setWizard(false), onCompleted: () => setWizard(false),
@ -81,58 +81,60 @@ const CashOut = ({ name: SCREEN_KEY }) => {
const wasNeverEnabled = it => R.compose(R.length, R.keys)(it) === 1 const wasNeverEnabled = it => R.compose(R.length, R.keys)(it) === 1
return ( return (
<> !loading && (
<TitleSection title="Cash-out"> <>
<div className={classes.fudgeFactor}> <TitleSection title="Cash-out">
<P>Transaction fudge factor</P> <div className={classes.fudgeFactor}>
<Switch <P>Transaction fudge factor</P>
checked={fudgeFactorActive} <Switch
onChange={event => { checked={fudgeFactorActive}
save({ fudgeFactorActive: event.target.checked }) onChange={event => {
}} save({ fudgeFactorActive: event.target.checked })
value={fudgeFactorActive} }}
/> value={fudgeFactorActive}
<Label2 className={classes.switchLabel}> />
{fudgeFactorActive ? 'On' : 'Off'} <Label2 className={classes.switchLabel}>
</Label2> {fudgeFactorActive ? 'On' : 'Off'}
<Tooltip width={304}> </Label2>
<P> <HoverableTooltip width={304}>
Automatically accept customer deposits as complete if their <P>
received amount is 100 crypto atoms or less. Automatically accept customer deposits as complete if their
</P> received amount is 100 crypto atoms or less.
<P> </P>
(Crypto atoms are the smallest unit in each cryptocurrency. E.g., <P>
satoshis in Bitcoin, or wei in Ethereum.) (Crypto atoms are the smallest unit in each cryptocurrency.
</P> E.g., satoshis in Bitcoin, or wei in Ethereum.)
</Tooltip> </P>
</div> </HoverableTooltip>
</TitleSection> </div>
<EditableTable </TitleSection>
namespaces={R.map(R.path(['deviceId']))(machines)} <EditableTable
data={config} namespaces={R.map(R.path(['deviceId']))(machines)}
stripeWhen={wasNeverEnabled} data={config}
enableEdit stripeWhen={wasNeverEnabled}
editWidth={134} enableEdit
enableToggle editWidth={134}
toggleWidth={109} enableToggle
onToggle={onToggle} toggleWidth={109}
save={save} onToggle={onToggle}
error={error?.message}
validationSchema={DenominationsSchema}
disableRowEdit={R.compose(R.not, R.path(['active']))}
elements={getElements(machines, locale, classes)}
/>
{R.isEmpty(machines) && <EmptyTable message="No machines so far" />}
{wizard && (
<Wizard
machine={R.find(R.propEq('deviceId', wizard))(machines)}
onClose={() => setWizard(false)}
save={save} save={save}
error={error?.message} error={error?.message}
locale={locale} validationSchema={DenominationsSchema}
disableRowEdit={R.compose(R.not, R.path(['active']))}
elements={getElements(machines, locale, classes)}
/> />
)} {R.isEmpty(machines) && <EmptyTable message="No machines so far" />}
</> {wizard && (
<Wizard
machine={R.find(R.propEq('deviceId', wizard))(machines)}
onClose={() => setWizard(false)}
save={save}
error={error?.message}
locale={locale}
/>
)}
</>
)
) )
} }

View file

@ -8,6 +8,12 @@ import { getBillOptions } from 'src/utils/bill-options'
import { CURRENCY_MAX } from 'src/utils/constants' import { CURRENCY_MAX } from 'src/utils/constants'
import { transformNumber } from 'src/utils/number' import { transformNumber } from 'src/utils/number'
const widthsByNumberOfCassettes = {
2: { machine: 300, cassette: 225, zeroConf: 200 },
3: { machine: 210, cassette: 180, zeroConf: 200 },
4: { machine: 200, cassette: 150, zeroConf: 150 }
}
const DenominationsSchema = Yup.object().shape({ const DenominationsSchema = Yup.object().shape({
cassette1: Yup.number() cassette1: Yup.number()
.label('Cassette 1') .label('Cassette 1')
@ -60,7 +66,7 @@ const getElements = (machines, locale = {}, classes) => {
{ {
name: 'id', name: 'id',
header: 'Machine', header: 'Machine',
width: 300, width: widthsByNumberOfCassettes[maxNumberOfCassettes].machine,
view: it => machines.find(({ deviceId }) => deviceId === it).name, view: it => machines.find(({ deviceId }) => deviceId === it).name,
size: 'sm', size: 'sm',
editable: false editable: false
@ -76,7 +82,7 @@ const getElements = (machines, locale = {}, classes) => {
size: 'sm', size: 'sm',
stripe: true, stripe: true,
textAlign: 'right', textAlign: 'right',
width: (maxNumberOfCassettes > 2 ? 600 : 460) / maxNumberOfCassettes, width: widthsByNumberOfCassettes[maxNumberOfCassettes].cassette,
suffix: fiatCurrency, suffix: fiatCurrency,
bold: bold, bold: bold,
view: it => it, view: it => it,
@ -99,7 +105,7 @@ const getElements = (machines, locale = {}, classes) => {
size: 'sm', size: 'sm',
stripe: true, stripe: true,
textAlign: 'right', textAlign: 'right',
width: maxNumberOfCassettes > 2 ? 150 : 290, width: widthsByNumberOfCassettes[maxNumberOfCassettes].zeroConf,
input: NumberInput, input: NumberInput,
inputProps: { inputProps: {
decimalPlaces: 0 decimalPlaces: 0

View file

@ -160,6 +160,7 @@ const CommissionsList = memo(
default={ORDER_OPTIONS[0]} default={ORDER_OPTIONS[0]}
items={ORDER_OPTIONS} items={ORDER_OPTIONS}
selectedItem={orderProp} selectedItem={orderProp}
defaultAsFilter
/> />
</div> </div>
<div className={classes.tableWrapper}> <div className={classes.tableWrapper}>
@ -172,6 +173,7 @@ const CommissionsList = memo(
validationSchema={getListCommissionsSchema(localeConfig)} validationSchema={getListCommissionsSchema(localeConfig)}
data={tableData} data={tableData}
elements={commissionsList(data, currency)} elements={commissionsList(data, currency)}
orderedBy={orderProp}
/> />
</div> </div>
</div> </div>

View file

@ -23,7 +23,7 @@ const ALL_COINS = {
code: 'ALL_COINS' code: 'ALL_COINS'
} }
const cashInAndOutHeaderStyle = { marginLeft: 6 } const cashInAndOutHeaderStyle = { marginLeft: 6, whiteSpace: 'nowrap' }
const cashInHeader = ( const cashInHeader = (
<div> <div>
@ -488,7 +488,7 @@ const getListCommissionsFields = (getData, currency, defaults) => {
{ {
name: 'cryptoCurrencies', name: 'cryptoCurrencies',
display: 'Crypto Currency', display: 'Crypto Currency',
width: 265, width: 255,
view: R.prop(0), view: R.prop(0),
size: 'sm', size: 'sm',
editable: false editable: false
@ -510,7 +510,7 @@ const getListCommissionsFields = (getData, currency, defaults) => {
header: cashOutHeader, header: cashOutHeader,
name: 'cashOut', name: 'cashOut',
display: 'Cash-out', display: 'Cash-out',
width: 130, width: 140,
input: NumberInput, input: NumberInput,
textAlign: 'right', textAlign: 'right',
greenText: true, greenText: true,

View file

@ -8,7 +8,7 @@ import { useState, React } from 'react'
import ErrorMessage from 'src/components/ErrorMessage' import ErrorMessage from 'src/components/ErrorMessage'
import PromptWhenDirty from 'src/components/PromptWhenDirty' import PromptWhenDirty from 'src/components/PromptWhenDirty'
import { MainStatus } from 'src/components/Status' import { MainStatus } from 'src/components/Status'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { ActionButton } from 'src/components/buttons' import { ActionButton } from 'src/components/buttons'
import { Label1, P, H3 } from 'src/components/typography' import { Label1, P, H3 } from 'src/components/typography'
import { import {
@ -148,7 +148,7 @@ const EditableCard = ({
<div className={classes.cardHeader}> <div className={classes.cardHeader}>
{titleIcon} {titleIcon}
<H3 className={classes.cardTitle}>{title}</H3> <H3 className={classes.cardTitle}>{title}</H3>
<Tooltip width={304}></Tooltip> <HoverableTooltip width={304}></HoverableTooltip>
</div> </div>
{state && authorize && ( {state && authorize && (
<div className={classnames(label1ClassNames)}> <div className={classnames(label1ClassNames)}>

View file

@ -6,7 +6,7 @@ import * as Yup from 'yup'
import ErrorMessage from 'src/components/ErrorMessage' import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal' import Modal from 'src/components/Modal'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { Button } from 'src/components/buttons' import { Button } from 'src/components/buttons'
import { NumberInput, Autocomplete } from 'src/components/inputs/formik' import { NumberInput, Autocomplete } from 'src/components/inputs/formik'
import { H3, TL1, P } from 'src/components/typography' import { H3, TL1, P } from 'src/components/typography'
@ -99,7 +99,7 @@ const IndividualDiscountModal = ({
<div> <div>
<div className={classes.discountRateWrapper}> <div className={classes.discountRateWrapper}>
<H3>Define discount rate</H3> <H3>Define discount rate</H3>
<Tooltip width={304}> <HoverableTooltip width={304}>
<P> <P>
This is a percentage discount off of your existing This is a percentage discount off of your existing
commission rates for a customer entering this code at commission rates for a customer entering this code at
@ -110,7 +110,7 @@ const IndividualDiscountModal = ({
code is set for 50%, then you'll instead be charging 4% code is set for 50%, then you'll instead be charging 4%
on transactions using the code. on transactions using the code.
</P> </P>
</Tooltip> </HoverableTooltip>
</div> </div>
<div className={classes.discountInput}> <div className={classes.discountInput}>
<Field <Field

View file

@ -6,7 +6,7 @@ import * as Yup from 'yup'
import ErrorMessage from 'src/components/ErrorMessage' import ErrorMessage from 'src/components/ErrorMessage'
import Modal from 'src/components/Modal' import Modal from 'src/components/Modal'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { Button } from 'src/components/buttons' import { Button } from 'src/components/buttons'
import { TextInput, NumberInput } from 'src/components/inputs/formik' import { TextInput, NumberInput } from 'src/components/inputs/formik'
import { H3, TL1, P } from 'src/components/typography' import { H3, TL1, P } from 'src/components/typography'
@ -69,7 +69,7 @@ const PromoCodesModal = ({ showModal, onClose, errorMsg, addCode }) => {
/> />
<div className={classes.modalLabel2Wrapper}> <div className={classes.modalLabel2Wrapper}>
<H3 className={classes.modalLabel2}>Define discount rate</H3> <H3 className={classes.modalLabel2}>Define discount rate</H3>
<Tooltip width={304}> <HoverableTooltip width={304}>
<P> <P>
This is a percentage discount off of your existing This is a percentage discount off of your existing
commission rates for a customer entering this code at the commission rates for a customer entering this code at the
@ -80,7 +80,7 @@ const PromoCodesModal = ({ showModal, onClose, errorMsg, addCode }) => {
set for 50%, then you'll instead be charging 4% on set for 50%, then you'll instead be charging 4% on
transactions using the code. transactions using the code.
</P> </P>
</Tooltip> </HoverableTooltip>
</div> </div>
<div className={classes.discountInput}> <div className={classes.discountInput}>
<Field <Field

View file

@ -5,7 +5,7 @@ import * as R from 'ramda'
import React from 'react' import React from 'react'
import Stepper from 'src/components/Stepper' import Stepper from 'src/components/Stepper'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { Button } from 'src/components/buttons' import { Button } from 'src/components/buttons'
import { Cashbox } from 'src/components/inputs/cashbox/Cashbox' import { Cashbox } from 'src/components/inputs/cashbox/Cashbox'
import { NumberInput, RadioGroup } from 'src/components/inputs/formik' import { NumberInput, RadioGroup } from 'src/components/inputs/formik'
@ -188,12 +188,12 @@ const WizardStep = ({
classes.centerAlignment classes.centerAlignment
)}> )}>
<P>Since previous update</P> <P>Since previous update</P>
<Tooltip width={215}> <HoverableTooltip width={215}>
<P> <P>
Number of bills inside the cash box, since the last Number of bills inside the cash box, since the last
cash box changes. cash box changes.
</P> </P>
</Tooltip> </HoverableTooltip>
</div> </div>
<div <div
className={classnames( className={classnames(

View file

@ -53,7 +53,7 @@ const Notifications = ({
const [error, setError] = useState(null) const [error, setError] = useState(null)
const [editingKey, setEditingKey] = useState(null) const [editingKey, setEditingKey] = useState(null)
const { data } = useQuery(GET_INFO) const { data, loading } = useQuery(GET_INFO)
const [saveConfig] = useMutation(SAVE_CONFIG, { const [saveConfig] = useMutation(SAVE_CONFIG, {
refetchQueries: ['getData'], refetchQueries: ['getData'],
@ -101,40 +101,47 @@ const Notifications = ({
} }
return ( return (
<NotificationsCtx.Provider value={contextValue}> !loading && (
{displayTitle && <TitleSection title="Notifications" />} <NotificationsCtx.Provider value={contextValue}>
{displaySetup && ( {displayTitle && <TitleSection title="Notifications" />}
<Section title="Setup" error={error && !section}> {displaySetup && (
<Setup forceDisable={!!editingKey} wizard={wizard} /> <Section title="Setup" error={error && !section}>
</Section> <Setup forceDisable={!!editingKey} wizard={wizard} />
)} </Section>
{displayTransactionAlerts && ( )}
<Section title="Transaction alerts" error={error && section === 'tx'}> {displayTransactionAlerts && (
<TransactionAlerts section="tx" fieldWidth={FIELDS_WIDTH} /> <Section title="Transaction alerts" error={error && section === 'tx'}>
</Section> <TransactionAlerts section="tx" fieldWidth={FIELDS_WIDTH} />
)} </Section>
{displayFiatAlerts && ( )}
<Section {displayFiatAlerts && (
title="Fiat balance alerts" <Section
error={error && section === 'fiat'}> title="Fiat balance alerts"
<FiatBalanceAlerts section="fiat" max={100} fieldWidth={50} /> error={error && section === 'fiat'}>
{displayOverrides && <FiatBalanceOverrides section="fiat" />} <FiatBalanceAlerts section="fiat" max={100} fieldWidth={50} />
</Section> {displayOverrides && (
)} <FiatBalanceOverrides
{displayCryptoAlerts && ( config={fromNamespace(namespaces.CASH_OUT)(data?.config)}
<Section section="fiat"
title="Crypto balance alerts" />
error={error && section === 'crypto'}> )}
<CryptoBalanceAlerts section="crypto" fieldWidth={FIELDS_WIDTH} /> </Section>
{displayOverrides && ( )}
<CryptoBalanceOverrides {displayCryptoAlerts && (
section="crypto" <Section
fieldWidth={FIELDS_WIDTH} title="Crypto balance alerts"
/> error={error && section === 'crypto'}>
)} <CryptoBalanceAlerts section="crypto" fieldWidth={FIELDS_WIDTH} />
</Section> {displayOverrides && (
)} <CryptoBalanceOverrides
</NotificationsCtx.Provider> section="crypto"
fieldWidth={FIELDS_WIDTH}
/>
)}
</Section>
)}
</NotificationsCtx.Provider>
)
) )
} }

View file

@ -5,6 +5,7 @@ import * as Yup from 'yup'
import { Table as EditableTable } from 'src/components/editableTable' import { Table as EditableTable } from 'src/components/editableTable'
import { NumberInput } from 'src/components/inputs/formik/' import { NumberInput } from 'src/components/inputs/formik/'
import Autocomplete from 'src/components/inputs/formik/Autocomplete' import Autocomplete from 'src/components/inputs/formik/Autocomplete'
import { fromNamespace } from 'src/utils/config'
import { transformNumber } from 'src/utils/number' import { transformNumber } from 'src/utils/number'
import NotificationsCtx from '../NotificationsContext' import NotificationsCtx from '../NotificationsContext'
@ -23,7 +24,13 @@ const CASSETTE_LIST = [
CASSETTE_4_KEY CASSETTE_4_KEY
] ]
const FiatBalanceOverrides = ({ section }) => { const widthsByNumberOfCassettes = {
2: { machine: 230, cassette: 250 },
3: { machine: 216, cassette: 270 },
4: { machine: 210, cassette: 204 }
}
const FiatBalanceOverrides = ({ config, section }) => {
const { const {
machines = [], machines = [],
data, data,
@ -36,9 +43,13 @@ const FiatBalanceOverrides = ({ section }) => {
const setupValues = data?.fiatBalanceOverrides ?? [] const setupValues = data?.fiatBalanceOverrides ?? []
const innerSetEditing = it => setEditing(NAME, it) const innerSetEditing = it => setEditing(NAME, it)
const cashoutConfig = it => fromNamespace(it)(config)
const overridenMachines = R.map(override => override.machine, setupValues) const overridenMachines = R.map(override => override.machine, setupValues)
const suggestionFilter = R.filter( const suggestionFilter = R.filter(
it => !R.contains(it.deviceId, overridenMachines) it =>
!R.includes(it.deviceId, overridenMachines) &&
cashoutConfig(it.deviceId).active
) )
const suggestions = suggestionFilter(machines) const suggestions = suggestionFilter(machines)
@ -114,7 +125,7 @@ const FiatBalanceOverrides = ({ section }) => {
const elements = [ const elements = [
{ {
name: MACHINE_KEY, name: MACHINE_KEY,
width: 238, width: widthsByNumberOfCassettes[maxNumberOfCassettes].machine,
size: 'sm', size: 'sm',
view: viewMachine, view: viewMachine,
input: Autocomplete, input: Autocomplete,
@ -132,7 +143,7 @@ const FiatBalanceOverrides = ({ section }) => {
elements.push({ elements.push({
name: `fillingPercentageCassette${it}`, name: `fillingPercentageCassette${it}`,
display: `Cash cassette ${it}`, display: `Cash cassette ${it}`,
width: 155, width: widthsByNumberOfCassettes[maxNumberOfCassettes].cassette,
textAlign: 'right', textAlign: 'right',
doubleHeader: 'Cash Cassette Empty', doubleHeader: 'Cash Cassette Empty',
bold: true, bold: true,

View file

@ -16,7 +16,7 @@ import { startCase } from 'src/utils/string'
import NotificationsCtx from '../NotificationsContext' import NotificationsCtx from '../NotificationsContext'
const channelSize = 129 const channelSize = 229
const sizes = { const sizes = {
balance: 152, balance: 152,
transactions: 184, transactions: 184,
@ -26,7 +26,7 @@ const sizes = {
active: 263 active: 263
} }
const Row = ({ namespace, forceDisable }) => { const Row = ({ namespace, forceDisable, shouldUpperCase }) => {
const { data: rawData, save: rawSave } = useContext(NotificationsCtx) const { data: rawData, save: rawSave } = useContext(NotificationsCtx)
const save = R.compose(rawSave(null), toNamespace(namespace)) const save = R.compose(rawSave(null), toNamespace(namespace))
@ -53,7 +53,9 @@ const Row = ({ namespace, forceDisable }) => {
return ( return (
<Tr> <Tr>
<Td width={channelSize}>{startCase(namespace)}</Td> <Td width={channelSize}>
{shouldUpperCase ? R.toUpper(namespace) : startCase(namespace)}
</Td>
<Cell name="balance" disabled={disabled} /> <Cell name="balance" disabled={disabled} />
<Cell name="transactions" disabled={disabled} /> <Cell name="transactions" disabled={disabled} />
<Cell name="compliance" disabled={disabled} /> <Cell name="compliance" disabled={disabled} />
@ -84,7 +86,7 @@ const Setup = ({ wizard, forceDisable }) => {
</THead> </THead>
<TBody> <TBody>
<Row namespace="email" forceDisable={forceDisable} /> <Row namespace="email" forceDisable={forceDisable} />
<Row namespace="sms" forceDisable={forceDisable} /> <Row namespace="sms" shouldUpperCase forceDisable={forceDisable} />
<Row namespace="notificationCenter" forceDisable={forceDisable} /> <Row namespace="notificationCenter" forceDisable={forceDisable} />
</TBody> </TBody>
</Table> </Table>

View file

@ -3,7 +3,7 @@ import { makeStyles } from '@material-ui/core/styles'
import gql from 'graphql-tag' import gql from 'graphql-tag'
import React, { memo } from 'react' import React, { memo } from 'react'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable' import { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable'
import { Switch } from 'src/components/inputs' import { Switch } from 'src/components/inputs'
import { H4, P, Label2 } from 'src/components/typography' import { H4, P, Label2 } from 'src/components/typography'
@ -66,7 +66,7 @@ const CoinATMRadar = memo(({ wizard }) => {
<div> <div>
<div className={classes.header}> <div className={classes.header}>
<H4>Coin ATM Radar share settings</H4> <H4>Coin ATM Radar share settings</H4>
<Tooltip width={304}> <HoverableTooltip width={304}>
<P> <P>
For details on configuring this panel, please read the relevant For details on configuring this panel, please read the relevant
knowledgebase article{' '} knowledgebase article{' '}
@ -78,7 +78,7 @@ const CoinATMRadar = memo(({ wizard }) => {
</a> </a>
. .
</P> </P>
</Tooltip> </HoverableTooltip>
</div> </div>
<Row <Row
title={'Share information?'} title={'Share information?'}

View file

@ -5,7 +5,7 @@ import gql from 'graphql-tag'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useState } from 'react' import React, { useState } from 'react'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { Link } from 'src/components/buttons' import { Link } from 'src/components/buttons'
import { Switch } from 'src/components/inputs' import { Switch } from 'src/components/inputs'
import TitleSection from 'src/components/layout/TitleSection' import TitleSection from 'src/components/layout/TitleSection'
@ -141,13 +141,13 @@ const Triggers = () => {
<Label2 className={classes.switchLabel}> <Label2 className={classes.switchLabel}>
{rejectAddressReuse ? 'On' : 'Off'} {rejectAddressReuse ? 'On' : 'Off'}
</Label2> </Label2>
<Tooltip width={304}> <HoverableTooltip width={304}>
<P> <P>
This option requires a user to scan a different cryptocurrency This option requires a user to scan a different cryptocurrency
address if they attempt to scan one that had been previously address if they attempt to scan one that had been previously
used for a transaction in your network used for a transaction in your network
</P> </P>
</Tooltip> </HoverableTooltip>
</Box> </Box>
</Box> </Box>
)} )}

View file

@ -5,7 +5,7 @@ import gql from 'graphql-tag'
import React, { useState } from 'react' import React, { useState } from 'react'
import InfoMessage from 'src/components/InfoMessage' import InfoMessage from 'src/components/InfoMessage'
import { Tooltip } from 'src/components/Tooltip' import { HoverableTooltip } from 'src/components/Tooltip'
import { Button, SupportLinkButton } from 'src/components/buttons' import { Button, SupportLinkButton } from 'src/components/buttons'
import { RadioGroup } from 'src/components/inputs' import { RadioGroup } from 'src/components/inputs'
import { H1, H4, P } from 'src/components/typography' import { H1, H4, P } from 'src/components/typography'
@ -102,7 +102,7 @@ function Twilio({ doContinue }) {
<H4 noMargin className={classnames(titleClasses)}> <H4 noMargin className={classnames(titleClasses)}>
Will you setup a two way machine or compliance? Will you setup a two way machine or compliance?
</H4> </H4>
<Tooltip width={304}> <HoverableTooltip width={304}>
<P> <P>
Two-way machines allow your customers not only to buy (cash-in) Two-way machines allow your customers not only to buy (cash-in)
but also sell cryptocurrencies (cash-out). but also sell cryptocurrencies (cash-out).
@ -111,7 +111,7 @@ function Twilio({ doContinue }) {
Youll need an SMS service for cash-out transactions and for any Youll need an SMS service for cash-out transactions and for any
compliance triggers compliance triggers
</P> </P>
</Tooltip> </HoverableTooltip>
</Box> </Box>
<RadioGroup <RadioGroup