partial: notifications, op info, services

This commit is contained in:
Rafael Taranto 2025-05-07 13:22:54 +01:00
parent a90833726e
commit 6d6edf578c
22 changed files with 290 additions and 795 deletions

View file

@ -35,7 +35,7 @@ const cashboxStyles = {
border: '4px solid'
},
emptyPart: {
backgroundColor: 'white',
backgroundColor: 'var(--ghost)',
height: ({ percent }) => `${100 - percent}%`,
position: 'relative',
'& > p': {

View file

@ -1,20 +0,0 @@
export default {
cryptoBalanceAlerts: {
display: 'flex',
marginBottom: 36,
height: 135,
alignItems: 'center'
},
cryptoBalanceAlertsForm: {
width: 222,
marginRight: 32
},
cryptoBalanceAlertsSecondForm: {
marginLeft: 50
},
vertSeparator: {
width: 1,
height: '100%',
borderRight: [[1, 'solid', 'black']]
}
}

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import React from 'react'
import { H4 } from 'src/components/typography'
import DisabledEditIcon from 'src/styling/icons/action/edit/disabled.svg?react'
@ -6,27 +5,23 @@ import EditIcon from 'src/styling/icons/action/edit/enabled.svg?react'
import { Link, IconButton } from 'src/components/buttons'
import styles from './EditHeader.styles'
const useStyles = makeStyles(styles)
const Header = ({ title, editing, disabled, setEditing }) => {
const classes = useStyles()
return (
<div className={classes.header}>
<H4 className={classes.title}>{title}</H4>
<div className="flex items-center m-0 mb-4 h-7">
<H4 noMargin className="overflow-hidden whitespace-nowrap text-ellipsis">
{title}
</H4>
{!editing && (
<IconButton
onClick={() => setEditing(true)}
className={classes.button}
className="border-0 bg-transparent shrink-0 cursor-pointer ml-2"
disabled={disabled}
size="large">
{disabled ? <DisabledEditIcon /> : <EditIcon />}
</IconButton>
)}
{editing && (
<div className={classes.editingButtons}>
<div className="flex ml-4 justify-between shrink-0 w-27">
<Link color="primary" type="submit">
Save
</Link>
@ -36,7 +31,7 @@ const Header = ({ title, editing, disabled, setEditing }) => {
</div>
)}
</div>
);
)
}
export default Header

View file

@ -1,29 +0,0 @@
export default {
header: {
display: 'flex',
alignItems: 'center',
marginBottom: 16,
height: 26,
margin: 0
},
title: {
flexShrink: 2,
margin: 0,
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis'
},
button: {
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer',
marginLeft: 8
},
editingButtons: {
display: 'flex',
flexShrink: 0,
marginLeft: 16,
justifyContent: 'space-between',
width: 110
}
}

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import { useFormikContext, Field as FormikField } from 'formik'
import React from 'react'
@ -6,10 +5,6 @@ import { Label1, Info1, TL2 } from 'src/components/typography'
import { NumberInput } from 'src/components/inputs/formik'
import styles from './EditableNumber.styles'
const useStyles = makeStyles(styles)
const EditableNumber = ({
label,
name,
@ -20,20 +15,21 @@ const EditableNumber = ({
decimalPlaces = 0,
width = 80
}) => {
const classes = useStyles({ width, editing })
const { values } = useFormikContext()
const classNames = {
[classes.fieldWrapper]: true,
'h-13': true,
className
}
return (
<div className={classnames(classNames)}>
{label && <Label1 className={classes.label}>{label}</Label1>}
<div className={classes.valueWrapper}>
{label && <Label1 noMargin>{label}</Label1>}
<div className="flex items-baseline">
{!editing && (
<Info1 className={classes.text}>{displayValue(values[name])}</Info1>
<Info1 noMargin className="my-2">
{displayValue(values[name])}
</Info1>
)}
{editing && (
<FormikField
@ -47,7 +43,9 @@ const EditableNumber = ({
decimalPlaces={decimalPlaces}
/>
)}
<TL2 className={classes.decoration}>{decoration}</TL2>
<TL2 noMargin className="ml-2">
{decoration}
</TL2>
</div>
</div>
)

View file

@ -1,18 +0,0 @@
export default {
text: {
margin: [[7, 0, 7, 1]]
},
fieldWrapper: {
height: 53
},
valueWrapper: {
display: 'flex',
alignItems: 'baseline'
},
label: {
margin: 0
},
decoration: {
margin: [[0, 0, 0, 7]]
}
}

View file

@ -1,31 +1,24 @@
import { makeStyles } from '@mui/styles'
import React, { useContext } from 'react'
import NotificationsCtx from '../NotificationsContext'
import SingleFieldEditableNumber from '../components/SingleFieldEditableNumber'
import styles from './CryptoBalanceAlerts.styles'
const LOW_BALANCE_KEY = 'cryptoLowBalance'
const HIGH_BALANCE_KEY = 'cryptoHighBalance'
const useStyles = makeStyles(styles)
const CryptoBalanceAlerts = ({ section, fieldWidth }) => {
const classes = useStyles()
const { data, save, currency, setEditing, isEditing, isDisabled } =
useContext(NotificationsCtx)
return (
<div className={classes.cryptoBalanceAlerts}>
<div className="flex mb-9 h-34 items-center gap-12">
<SingleFieldEditableNumber
name={LOW_BALANCE_KEY}
data={data}
save={save}
section={section}
decoration={currency}
className={classes.cryptoBalanceAlertsForm}
className="w-50"
title="Default (Low balance)"
label="Alert me under"
editing={isEditing(LOW_BALANCE_KEY)}
@ -34,7 +27,7 @@ const CryptoBalanceAlerts = ({ section, fieldWidth }) => {
width={fieldWidth}
/>
<div className={classes.vertSeparator} />
<div className="w-[1px] h-full border-r border-r-comet" />
<SingleFieldEditableNumber
name={HIGH_BALANCE_KEY}
@ -42,7 +35,6 @@ const CryptoBalanceAlerts = ({ section, fieldWidth }) => {
section={section}
save={save}
decoration={currency}
className={classes.cryptoBalanceAlertsSecondForm}
title="Default (High balance)"
label="Alert me over"
editing={isEditing(HIGH_BALANCE_KEY)}

View file

@ -1,20 +0,0 @@
export default {
cryptoBalanceAlerts: {
display: 'flex',
marginBottom: 36,
height: 135,
alignItems: 'center'
},
cryptoBalanceAlertsForm: {
width: 222,
marginRight: 32
},
cryptoBalanceAlertsSecondForm: {
marginLeft: 50
},
vertSeparator: {
width: 1,
height: '100%',
borderRight: [[1, 'solid', 'black']]
}
}

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import { Form, Formik } from 'formik'
import * as R from 'ramda'
import React, { useContext } from 'react'
@ -13,10 +12,6 @@ import NotificationsCtx from '../NotificationsContext'
import Header from '../components/EditHeader'
import EditableNumber from '../components/EditableNumber'
import styles from './FiatBalanceAlerts.styles'
const useStyles = makeStyles(styles)
const CASH_IN_KEY = 'fiatBalanceAlertsCashIn'
const CASH_OUT_KEY = 'fiatBalanceAlertsCashOut'
const RECYCLER_STACKER_KEY = 'fiatBalanceAlertsRecyclerStacker'
@ -34,8 +29,6 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
save,
machines = []
} = useContext(NotificationsCtx)
const classes = useStyles()
const maxNumberOfCassettes = Math.max(
...R.map(it => it.numberOfCassettes, machines),
DEFAULT_NUMBER_OF_CASSETTES
@ -97,8 +90,8 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
setEditing(CASH_OUT_KEY, false)
}}>
{({ values }) => (
<>
<Form className={classes.form}>
<div className="flex flex-col gap-9">
<Form>
<PromptWhenDirty />
<Header
title="Cash box"
@ -106,24 +99,16 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
disabled={isDisabled(CASH_IN_KEY)}
setEditing={it => setEditing(CASH_IN_KEY, it)}
/>
<div className={classes.wrapper}>
<div className={classes.first}>
<div className={classes.row}>
<div className={classes.col2}>
<EditableNumber
label="Alert me over"
name="cashInAlertThreshold"
editing={isEditing(CASH_IN_KEY)}
displayValue={x => (x === '' ? '-' : x)}
decoration="notes"
width={fieldWidth}
/>
</div>
</div>
</div>
</div>
<EditableNumber
label="Alert me over"
name="cashInAlertThreshold"
editing={isEditing(CASH_IN_KEY)}
displayValue={x => (x === '' ? '-' : x)}
decoration="notes"
width={fieldWidth}
/>
</Form>
<Form className={classes.form}>
<Form>
<PromptWhenDirty />
<Header
title="Cash out (Empty)"
@ -131,14 +116,12 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
disabled={isDisabled(CASH_OUT_KEY)}
setEditing={it => setEditing(CASH_OUT_KEY, it)}
/>
<div className={classes.wrapper}>
<div className="flex flex-wrap gap-8">
{R.map(
it => (
<>
<div className={classes.row}>
<div className="flex w-50 gap-4">
<Cashbox
labelClassName={classes.cashboxLabel}
emptyPartClassName={classes.cashboxEmptyPart}
percent={
values[`fillingPercentageCassette${it + 1}`] ??
data[`cassette${it + 1}`]
@ -148,8 +131,8 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
omitInnerPercentage
cashOut
/>
<div className={classes.col2}>
<TL2 className={classes.title}>Cassette {it + 1}</TL2>
<div className="w-30">
<TL2 className="mt-0">Cassette {it + 1}</TL2>
<EditableNumber
label="Alert me under"
name={`fillingPercentageCassette${it + 1}`}
@ -166,7 +149,7 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
)}
</div>
</Form>
<Form className={classes.form}>
<Form>
<PromptWhenDirty />
<Header
title="Cash recycling"
@ -174,14 +157,12 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
disabled={isDisabled(RECYCLER_STACKER_KEY)}
setEditing={it => setEditing(RECYCLER_STACKER_KEY, it)}
/>
<div className={classes.wrapper}>
<div className="flex flex-wrap gap-8">
{R.chain(
it => [
<>
<div className={classes.row}>
<div className="flex w-50 gap-4">
<Cashbox
labelClassName={classes.cashboxLabel}
emptyPartClassName={classes.cashboxEmptyPart}
percent={
values[
`fillingPercentageRecycler${(it + 1) * 2 - 1}`
@ -192,10 +173,8 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
omitInnerPercentage
cashOut
/>
<div className={classes.col2}>
<TL2 className={classes.title}>
Recycler {(it + 1) * 2 - 1}
</TL2>
<div className="w-30">
<TL2 className="mt-0">Recycler {(it + 1) * 2 - 1}</TL2>
<EditableNumber
label="Alert me under"
name={`fillingPercentageRecycler${(it + 1) * 2 - 1}`}
@ -208,10 +187,8 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
</div>
</>,
<>
<div className={classes.row}>
<div className="flex w-50 gap-4">
<Cashbox
labelClassName={classes.cashboxLabel}
emptyPartClassName={classes.cashboxEmptyPart}
percent={
values[`fillingPercentageRecycler${(it + 1) * 2}`] ??
data[`recycler${(it + 1) * 2}`]
@ -221,10 +198,8 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
omitInnerPercentage
cashOut
/>
<div className={classes.col2}>
<TL2 className={classes.title}>
Recycler {(it + 1) * 2}
</TL2>
<div className="w-30">
<TL2 className="mt-0">Recycler {(it + 1) * 2}</TL2>
<EditableNumber
label="Alert me under"
name={`fillingPercentageRecycler${(it + 1) * 2}`}
@ -241,7 +216,7 @@ const FiatBalance = ({ section, min = 0, max = 100, fieldWidth = 80 }) => {
)}
</div>
</Form>
</>
</div>
)}
</Formik>
)

View file

@ -1,31 +0,0 @@
import { backgroundColor } from 'src/styling/variables'
export default {
wrapper: {
display: 'flex'
},
form: {
marginBottom: 36
},
title: {
marginTop: 0
},
row: {
width: 236,
display: 'grid',
gridTemplateColumns: 'repeat(2,1fr)',
gridTemplateRows: '1fr',
gridColumnGap: 18,
gridRowGap: 0
},
col2: {
width: 136
},
cashboxLabel: {
marginRight: 4,
fontSize: 20
},
cashboxEmptyPart: {
backgroundColor: `${backgroundColor}`
}
}

View file

@ -1,4 +1,3 @@
import { makeStyles } from '@mui/styles'
import Switch from '@mui/material/Switch'
import * as R from 'ramda'
import React, { useContext } from 'react'
@ -74,12 +73,6 @@ const Row = ({
)
}
const useStyles = makeStyles({
wizardTable: {
width: 930
}
})
const Setup = ({ wizard, forceDisable }) => {
const {
data: rawData,
@ -120,9 +113,8 @@ const Setup = ({ wizard, forceDisable }) => {
]
const widthAdjust = wizard ? 20 : 0
const classes = useStyles()
return (
<Table className={wizard ? classes.wizardTable : null}>
<Table className={wizard ? 'w-233' : null}>
<THead>
<Th width={channelSize - widthAdjust}>Channel</Th>
{Object.keys(sizes).map(it => (

View file

@ -1,18 +1,11 @@
import { useQuery, useMutation, gql } from "@apollo/client";
import { makeStyles } from '@mui/styles'
import Switch from '@mui/material/Switch'
import { useQuery, useMutation, gql } from '@apollo/client'
import React, { memo } from 'react'
import { HelpTooltip } from 'src/components/Tooltip'
import { H4, P, Label2 } from 'src/components/typography'
import { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable'
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
import { SupportLinkButton } from '../../components/buttons'
import { global } from './OperatorInfo.styles'
const useStyles = makeStyles(global)
import SwitchRow from './components/SwitchRow.jsx'
import Header from './components/Header.jsx'
const GET_CONFIG = gql`
query getData {
@ -26,27 +19,7 @@ const SAVE_CONFIG = gql`
}
`
const Row = memo(({ title, disabled = false, checked, save, label }) => {
const classes = useStyles()
return (
<div className={classes.switchRow}>
<P>{title}</P>
<div className={classes.switch}>
<Switch
disabled={disabled}
checked={checked}
onChange={event => save && save(event.target.checked)}
/>
{label && <Label2>{label}</Label2>}
</div>
</div>
)
})
const CoinATMRadar = memo(({ wizard }) => {
const classes = useStyles()
const { data } = useQuery(GET_CONFIG)
const [saveConfig] = useMutation(SAVE_CONFIG, {
@ -63,53 +36,34 @@ const CoinATMRadar = memo(({ wizard }) => {
if (!coinAtmRadarConfig) return null
return (
<div className={classes.content}>
<div>
<div className={classes.header}>
<H4>Coin ATM Radar share settings</H4>
<HelpTooltip width={320}>
<P>
For details on configuring this panel, please read the relevant
knowledgebase article{' '}
<a
target="_blank"
rel="noopener noreferrer"
href="https://support.lamassu.is/hc/en-us/articles/360023720472-Coin-ATM-Radar">
here
</a>
.
</P>
<SupportLinkButton
link="https://support.lamassu.is/hc/en-us/articles/360023720472-Coin-ATM-Radar"
label="Lamassu Support Article"
bottomSpace="1"
/>
</HelpTooltip>
</div>
<Row
title={'Share information?'}
checked={coinAtmRadarConfig.active}
save={value => save({ active: value })}
label={coinAtmRadarConfig.active ? 'Yes' : 'No'}
/>
<BooleanPropertiesTable
editing={wizard}
title="Machine info"
data={coinAtmRadarConfig}
elements={[
{
name: 'commissions',
display: 'Commissions'
},
{
name: 'limitsAndVerification',
display: 'Limits and verification'
}
]}
save={save}
/>
</div>
</div>
<>
<Header
title="Coin ATM Radar share settings"
articleUrl="https://support.lamassu.is/hc/en-us/articles/360023720472-Coin-ATM-Radar"
tooltipText="For details on configuring this panel, please read the relevant knowledgebase article."
/>
<SwitchRow
title={'Share information?'}
checked={coinAtmRadarConfig.active}
save={value => save({ active: value })}
/>
<BooleanPropertiesTable
editing={wizard}
title="Machine info"
data={coinAtmRadarConfig}
elements={[
{
name: 'commissions',
display: 'Commissions'
},
{
name: 'limitsAndVerification',
display: 'Limits and verification'
}
]}
save={save}
/>
</>
)
})

View file

@ -1,70 +1,37 @@
import { useQuery, useMutation, gql } from "@apollo/client";
import { makeStyles } from '@mui/styles'
import Switch from '@mui/material/Switch'
import classnames from 'classnames'
import { useQuery, useMutation, gql } from '@apollo/client'
import { Form, Formik, Field as FormikField } from 'formik'
import * as R from 'ramda'
import React, { useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import PromptWhenDirty from 'src/components/PromptWhenDirty'
import { HelpTooltip } from 'src/components/Tooltip'
import { P, H4, Info3, Label1, Label2, Label3 } from 'src/components/typography'
import { H4, Info3, Label3 } from 'src/components/typography'
import EditIcon from 'src/styling/icons/action/edit/enabled.svg?react'
import WarningIcon from 'src/styling/icons/warning-icon/comet.svg?react'
import * as Yup from 'yup'
import { Link, IconButton, SupportLinkButton } from 'src/components/buttons'
import { Link, IconButton } from 'src/components/buttons'
import { TextInput } from 'src/components/inputs/formik'
import { fontSize5 } from 'src/styling/variables'
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
import { global } from './OperatorInfo.styles'
import SwitchRow from './components/SwitchRow.jsx'
import InfoMessage from './components/InfoMessage.jsx'
import Header from './components/Header.jsx'
const FIELD_WIDTH = 280
const fieldStyles = {
field: {
position: 'relative',
width: 280,
height: 48,
padding: [[0, 4, 4, 0]]
},
notEditing: {
display: 'flex',
flexDirection: 'column',
'& > p:first-child': {
height: 16,
lineHeight: '16px',
fontSize: fontSize5,
transformOrigin: 'left',
paddingLeft: 0,
margin: [[3, 0, 3, 0]]
},
'& > p:last-child': {
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
margin: 0
}
}
}
const fieldUseStyles = makeStyles(fieldStyles)
const Field = ({ editing, field, displayValue, ...props }) => {
const classes = fieldUseStyles()
const classNames = {
[classes.field]: true,
[classes.notEditing]: !editing
}
return (
<div className={classnames(classNames)}>
<div className="w-70 h-12 p-0 pl-1 pb-1">
{!editing && (
<>
<Label3>{field.label}</Label3>
<Info3>{displayValue(field.value)}</Info3>
<Label3 noMargin className="h-4 text-[13px] my-[3px]">
{field.label}
</Label3>
<Info3
noMargin
className="overflow-hidden whitespace-nowrap text-ellipsis">
{displayValue(field.value)}
</Info3>
</>
)}
{editing && (
@ -95,11 +62,7 @@ const SAVE_CONFIG = gql`
}
`
const contactUseStyles = makeStyles(global)
const ContactInfo = ({ wizard }) => {
const classes = contactUseStyles()
const [editing, setEditing] = useState(wizard || false)
const [error, setError] = useState(null)
@ -187,42 +150,17 @@ const ContactInfo = ({ wizard }) => {
return (
<>
<div className={classes.header}>
<H4>Contact information</H4>
<HelpTooltip width={320}>
<P>
For details on configuring this panel, please read the relevant
knowledgebase article:
</P>
<SupportLinkButton
link="https://support.lamassu.is/hc/en-us/articles/360033051732-Enabling-Operator-Info"
label="Lamassu Support Article"
bottomSpace="1"
/>
</HelpTooltip>
</div>
<div className={classes.switchRow}>
<P>Info card enabled?</P>
<div className={classes.switch}>
<Switch
checked={info.active}
onChange={event =>
save({
active: event.target.checked
})
}
/>
<Label2>{info.active ? 'Yes' : 'No'}</Label2>
</div>
</div>
<div className={classes.section}>
<div className={classes.header}>
<Header
title="Contact information"
articleUrl="https://support.lamassu.is/hc/en-us/articles/360033051732-Enabling-Operator-Info"
tooltipText="For details on configuring this panel, please read the relevant knowledgebase article:"
/>
<SwitchRow checked={info.active} save={save} title="Info card enabled?" />
<div>
<div className="flex items-center gap-4">
<H4>Info card</H4>
{!editing && (
<IconButton
className={classes.transparentButton}
onClick={() => setEditing(true)}
size="large">
<IconButton onClick={() => setEditing(true)} size="large">
<EditIcon />
</IconButton>
)}
@ -239,9 +177,9 @@ const ContactInfo = ({ wizard }) => {
setError(null)
}}>
{({ errors }) => (
<Form>
<Form className="flex flex-col gap-7 w-147">
<PromptWhenDirty />
<div className={classes.row}>
<div className="flex gap-6 justify-between">
<Field
field={findField('name')}
editing={editing}
@ -255,7 +193,7 @@ const ContactInfo = ({ wizard }) => {
onFocus={() => setError(null)}
/>
</div>
<div className={classes.row}>
<div className="flex gap-6">
<Field
field={findField('email')}
editing={editing}
@ -269,7 +207,7 @@ const ContactInfo = ({ wizard }) => {
onFocus={() => setError(null)}
/>
</div>
<div className={classes.row}>
<div className="flex gap-6">
<Field
field={findField('companyNumber')}
editing={editing}
@ -278,41 +216,34 @@ const ContactInfo = ({ wizard }) => {
/>
</div>
{editing && !!getErrorMsg(errors) && (
<ErrorMessage className={classes.formErrorMsg}>
{getErrorMsg(errors)}
</ErrorMessage>
<ErrorMessage>{getErrorMsg(errors)}</ErrorMessage>
)}
{editing && (
<div className="flex gap-10">
<Link color="primary" type="submit">
Save
</Link>
<Link color="secondary" type="reset">
Cancel
</Link>
{error && <ErrorMessage>Failed to save changes</ErrorMessage>}
</div>
)}
<div className={classnames(classes.row, classes.submit)}>
{editing && (
<>
<Link color="primary" type="submit">
Save
</Link>
<Link color="secondary" type="reset">
Cancel
</Link>
{error && (
<ErrorMessage>Failed to save changes</ErrorMessage>
)}
</>
)}
</div>
</Form>
)}
</Formik>
</div>
{!wizard && (
<div className={classnames(classes.section, classes.infoMessage)}>
<WarningIcon />
<Label1>
<>
<InfoMessage Icon={WarningIcon}>
Sharing your information with your customers through your machines
allows them to contact you in case there's a problem with a machine
in your network or a transaction.
</Label1>
</div>
</InfoMessage>
</>
)}
</>
);
)
}
export default ContactInfo

View file

@ -1,15 +1,11 @@
import { useQuery, useMutation, gql } from "@apollo/client";
import { makeStyles } from '@mui/styles'
import Switch from '@mui/material/Switch'
import { useQuery, useMutation, gql } from '@apollo/client'
import * as R from 'ramda'
import React, { memo } from 'react'
import { H4, P, Label2 } from 'src/components/typography'
import { H4 } from 'src/components/typography'
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
import { global } from './OperatorInfo.styles'
const useStyles = makeStyles(global)
import SwitchRow from './components/SwitchRow.jsx'
const GET_CONFIG = gql`
query getData {
@ -24,14 +20,26 @@ const SAVE_CONFIG = gql`
`
const MachineScreens = memo(({ wizard }) => {
const classes = useStyles()
const { data } = useQuery(GET_CONFIG)
const [saveConfig] = useMutation(SAVE_CONFIG, {
refetchQueries: () => ['getData']
})
const save = it => {
const formatConfig = R.compose(
toNamespace(namespaces.MACHINE_SCREENS),
toNamespace('rates'),
R.mergeRight(ratesScreenConfig)
)
return saveConfig({
variables: {
config: formatConfig({ active: it })
}
})
}
const machineScreensConfig =
data?.config && fromNamespace(namespaces.MACHINE_SCREENS, data.config)
@ -46,32 +54,12 @@ const MachineScreens = memo(({ wizard }) => {
return (
<>
<div className={classes.header}>
<H4>Rates screen</H4>
</div>
<div className={classes.switchRow}>
<P>Enable rates screen</P>
<div className={classes.switch}>
<Switch
checked={ratesScreenConfig.active}
onChange={event =>
saveConfig({
variables: {
config: R.compose(
toNamespace(namespaces.MACHINE_SCREENS),
toNamespace('rates')
)(
R.merge(ratesScreenConfig, {
active: event.target.checked
})
)
}
})
}
/>
<Label2>{ratesScreenConfig.active ? 'Yes' : 'No'}</Label2>
</div>
</div>
<H4>Rates screen</H4>
<SwitchRow
save={save}
title="Enable rates screen"
checked={ratesScreenConfig.active}
/>
</>
)
})

View file

@ -1,118 +0,0 @@
import { offColor } from 'src/styling/variables'
const global = {
content: {
display: 'flex'
},
header: {
display: 'flex',
alignItems: 'center',
position: 'relative',
flex: 'wrap'
},
section: {
marginBottom: 52
},
row: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 28,
width: 600,
'&:last-child': {
marginBottom: 0
}
},
switchRow: {
display: 'flex',
alignItems: 'center',
position: 'relative',
flex: 'wrap',
justifyContent: 'space-between',
width: 396
},
switch: {
display: 'flex',
alignItems: 'center'
},
submit: {
justifyContent: 'flex-start',
alignItems: 'center',
padding: [[0, 4, 4, 4]],
'& > button': {
marginRight: 40
}
},
transparentButton: {
'& > *': {
margin: 'auto 12px'
},
'& button': {
border: 'none',
backgroundColor: 'transparent',
cursor: 'pointer'
}
},
infoMessage: {
display: 'flex',
marginBottom: 52,
'& > p': {
width: 330,
color: offColor,
marginTop: 4,
marginLeft: 16
}
},
formErrorMsg: {
margin: [[0, 0, 20, 0]]
}
}
const fieldStyles = {
field: {
position: 'relative',
width: 280,
padding: [[0, 4, 4, 0]]
},
notEditing: {
display: 'flex',
flexDirection: 'column'
},
notEditingSingleLine: {
'& > p:first-child': {
height: 16,
lineHeight: '16px',
transform: 'scale(0.75)',
transformOrigin: 'left',
paddingLeft: 0,
margin: [[1, 0, 6, 0]]
},
'& > p:last-child': {
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
height: 25,
margin: 0
}
},
notEditingMultiline: {
'& > p:first-child': {
height: 16,
lineHeight: '16px',
transform: 'scale(0.75)',
transformOrigin: 'left',
paddingLeft: 0,
margin: [[1, 0, 5, 0]]
},
'& > p:last-child': {
width: 502,
height: 121,
overflowY: 'auto',
lineHeight: '19px',
wordWrap: 'anywhere',
margin: 0
}
}
}
export { global, fieldStyles }

View file

@ -1,19 +1,12 @@
import { useQuery, useMutation, gql } from "@apollo/client";
import { makeStyles } from '@mui/styles'
import Switch from '@mui/material/Switch'
import { useQuery, useMutation, gql } from '@apollo/client'
import * as R from 'ramda'
import React, { memo } from 'react'
import { HelpTooltip } from 'src/components/Tooltip'
import { H4, P, Label2 } from 'src/components/typography'
import { BooleanPropertiesTable } from 'src/components/booleanPropertiesTable'
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
import { SupportLinkButton } from '../../components/buttons'
import { global } from './OperatorInfo.styles'
const useStyles = makeStyles(global)
import Header from './components/Header.jsx'
import SwitchRow from './components/SwitchRow.jsx'
const GET_CONFIG = gql`
query getData {
@ -28,14 +21,23 @@ const SAVE_CONFIG = gql`
`
const ReceiptPrinting = memo(({ wizard }) => {
const classes = useStyles()
const { data } = useQuery(GET_CONFIG)
const [saveConfig] = useMutation(SAVE_CONFIG, {
refetchQueries: () => ['getData']
})
const saveSwitch = object => {
return saveConfig({
variables: {
config: toNamespace(
namespaces.RECEIPT,
R.mergeRight(receiptPrintingConfig, object)
)
}
})
}
const save = it =>
saveConfig({
variables: { config: toNamespace(namespaces.RECEIPT, it) }
@ -47,84 +49,26 @@ const ReceiptPrinting = memo(({ wizard }) => {
return (
<>
<div className={classes.header}>
<H4>Receipt options</H4>
<HelpTooltip width={320}>
<P>
For details on configuring this panel, please read the relevant
knowledgebase article:
</P>
<SupportLinkButton
link="https://support.lamassu.is/hc/en-us/articles/360058513951-Receipt-options-printers"
label="Lamassu Support Article"
bottomSpace="1"
/>
</HelpTooltip>
</div>
<div className={classes.switchRow}>
<P>Enable receipt printing</P>
<div className={classes.switch}>
<Switch
checked={receiptPrintingConfig.active}
onChange={event =>
saveConfig({
variables: {
config: toNamespace(
namespaces.RECEIPT,
R.merge(receiptPrintingConfig, {
active: event.target.checked
})
)
}
})
}
/>
<Label2>{receiptPrintingConfig.active ? 'Yes' : 'No'}</Label2>
</div>
</div>
<div className={classes.switchRow}>
<P>Automatic receipt printing</P>
<div className={classes.switch}>
<Switch
disabled={!receiptPrintingConfig.active}
checked={receiptPrintingConfig.automaticPrint}
onChange={event =>
saveConfig({
variables: {
config: toNamespace(
namespaces.RECEIPT,
R.merge(receiptPrintingConfig, {
automaticPrint: event.target.checked
})
)
}
})
}
/>
<Label2>{receiptPrintingConfig.automaticPrint ? 'Yes' : 'No'}</Label2>
</div>
</div>
<div className={classes.switchRow}>
<P>Offer SMS receipt</P>
<div className={classes.switch}>
<Switch
checked={receiptPrintingConfig.sms}
onChange={event =>
saveConfig({
variables: {
config: toNamespace(
namespaces.RECEIPT,
R.merge(receiptPrintingConfig, {
sms: event.target.checked
})
)
}
})
}
/>
<Label2>{receiptPrintingConfig.sms ? 'Yes' : 'No'}</Label2>
</div>
</div>
<Header
title="Receipt printing"
tooltipText="For details on configuring this panel, please read the relevant knowledgebase article."
articleUrl="https://support.lamassu.is/hc/en-us/articles/360058513951-Receipt-options-printers"
/>
<SwitchRow
title="Enable receipt printing"
checked={receiptPrintingConfig.active}
save={it => saveSwitch({ active: it })}
/>
<SwitchRow
title="Automatic receipt printing"
checked={receiptPrintingConfig.automaticPrint}
save={it => saveSwitch({ automaticPrint: it })}
/>
<SwitchRow
title="Offer SMS receipt"
checked={receiptPrintingConfig.sms}
save={it => saveSwitch({ sms: it })}
/>
<BooleanPropertiesTable
editing={wizard}
title={'Visible on the receipt (options)'}

View file

@ -1,24 +1,20 @@
import { useQuery, useMutation, gql } from "@apollo/client";
import { makeStyles } from '@mui/styles'
import Switch from '@mui/material/Switch'
import { useQuery, useMutation, gql } from '@apollo/client'
import classnames from 'classnames'
import { Form, Formik, Field as FormikField } from 'formik'
import * as R from 'ramda'
import React, { useState } from 'react'
import ErrorMessage from 'src/components/ErrorMessage'
import PromptWhenDirty from 'src/components/PromptWhenDirty'
import { HelpTooltip } from 'src/components/Tooltip'
import { H4, Info2, Info3, Label2, Label3, P } from 'src/components/typography'
import { Info2, Info3, Label3 } from 'src/components/typography'
import EditIcon from 'src/styling/icons/action/edit/enabled.svg?react'
import * as Yup from 'yup'
import { Link, IconButton, SupportLinkButton } from 'src/components/buttons'
import { Link, IconButton } from 'src/components/buttons'
import { TextInput } from 'src/components/inputs/formik'
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
import { global, fieldStyles } from './OperatorInfo.styles'
const useFieldStyles = makeStyles(fieldStyles)
import Header from './components/Header.jsx'
import SwitchRow from './components/SwitchRow.jsx'
const Field = ({
editing,
@ -32,21 +28,21 @@ const Field = ({
onFocus,
...props
}) => {
const classes = useFieldStyles()
const classNames = {
[classes.field]: true,
[classes.notEditing]: !editing,
[classes.notEditingSingleLine]: !editing && !multiline,
[classes.notEditingMultiline]: !editing && multiline
const info3ClassNames = {
'overflow-hidden whitespace-nowrap text-ellipsis h-6': !multiline,
'wrap-anywhere overflow-y-auto h-32 mt-4 leading-[23px]': multiline
}
return (
<div className={classnames(classNames)}>
<div className={`w-125 p-0 pl-1 pb-1`}>
{!editing && (
<>
<Label3>{label}</Label3>
<Info3>{value}</Info3>
<Label3 noMargin className="h-4 text-[13px] my-[3px] mb-1">
{label}
</Label3>
<Info3 noMargin className={classnames(info3ClassNames)}>
{value}
</Info3>
</>
)}
{editing && (
@ -81,8 +77,6 @@ const SAVE_CONFIG = gql`
}
`
const useTermsConditionsStyles = makeStyles(global)
const TermsConditions = () => {
const [error, setError] = useState(null)
const [editing, setEditing] = useState(false)
@ -95,8 +89,6 @@ const TermsConditions = () => {
onError: e => setError(e)
})
const classes = useTermsConditionsStyles()
const { data } = useQuery(GET_CONFIG)
const termsAndConditions =
@ -169,72 +161,30 @@ const TermsConditions = () => {
return (
<>
<div className={classes.header}>
<H4>Terms &amp; Conditions</H4>
<HelpTooltip width={320}>
<P>
For details on configuring this panel, please read the relevant
knowledgebase article:
</P>
<SupportLinkButton
link="https://support.lamassu.is/hc/en-us/articles/360015982211-Terms-and-Conditions"
label="Lamassu Support Article"
bottomSpace="1"
/>
</HelpTooltip>
</div>
<div className={classes.switchRow}>
<P>Show on screen</P>
<div className={classes.switch}>
<Switch
checked={showOnScreen}
onChange={event =>
save({
active: event.target.checked
})
}
/>
<Label2>{showOnScreen ? 'Yes' : 'No'}</Label2>
</div>
</div>
<div className={classes.switchRow}>
<P>
Capture customer photo on acceptance <br /> of Terms & Conditions
screen
</P>
<div className={classes.switch}>
<Switch
checked={tcPhoto}
onChange={event =>
save({
tcPhoto: event.target.checked
})
}
/>
<Label2>{tcPhoto ? 'Yes' : 'No'}</Label2>
</div>
</div>
<div className={classes.switchRow}>
<P>Add 7 seconds delay on screen</P>
<div className={classes.switch}>
<Switch
checked={addDelayOnScreen}
onChange={event =>
save({
delay: event.target.checked
})
}
/>
<Label2>{addDelayOnScreen ? 'Yes' : 'No'}</Label2>
</div>
</div>
<div className={classes.header}>
<Header
title="Terms & Conditions"
tooltipText="For details on configuring this panel, please read the relevant knowledgebase article:"
articleUrl="https://support.lamassu.is/hc/en-us/articles/360015982211-Terms-and-Conditions"
/>
<SwitchRow
title="Show on screen"
checked={showOnScreen}
save={it => save({ active: it })}
/>
<SwitchRow
title="Capture customer photo on acceptance of Terms & Conditions"
checked={tcPhoto}
save={it => save({ tcPhoto: it })}
/>
<SwitchRow
title="Add 7 seconds delay on screen"
checked={addDelayOnScreen}
save={it => save({ delay: it })}
/>
<div className="flex gap-3">
<Info2>Info card</Info2>
{!editing && (
<IconButton
className={classes.transparentButton}
onClick={() => setEditing(true)}
size="large">
<IconButton onClick={() => setEditing(true)} size="large">
<EditIcon />
</IconButton>
)}
@ -251,10 +201,10 @@ const TermsConditions = () => {
setError(null)
}}>
{({ errors }) => (
<Form>
<Form className="flex flex-col gap-6">
<PromptWhenDirty />
{fields.map((f, idx) => (
<div className={classes.row} key={idx}>
<div className="flex gap-7" key={idx}>
<Field
editing={editing}
name={f.name}
@ -268,7 +218,7 @@ const TermsConditions = () => {
/>
</div>
))}
<div className={classnames(classes.row, classes.submit)}>
<div className="flex gap-10">
{editing && (
<>
<Link color="primary" type="submit">
@ -288,7 +238,7 @@ const TermsConditions = () => {
)}
</Formik>
</>
);
)
}
export default TermsConditions

View file

@ -0,0 +1,20 @@
import React from 'react'
import { H4, P } from 'src/components/typography/index.jsx'
import { HelpTooltip } from 'src/components/Tooltip.jsx'
import { SupportLinkButton } from 'src/components/buttons/index.js'
const Header = ({ title, tooltipText, articleUrl }) => (
<div className="flex items-center">
<H4>{title}</H4>
<HelpTooltip width={320}>
<P>{tooltipText}</P>
<SupportLinkButton
link={articleUrl}
label="Lamassu Support Article"
bottomSpace="1"
/>
</HelpTooltip>
</div>
)
export default Header

View file

@ -0,0 +1,12 @@
import React from 'react'
import { Label1 } from 'src/components/typography'
const InfoMessage = ({ Icon, children }) => (
<div className="flex my-13 gap-4">
<Icon />
<Label1 className="w-83 text-comet mt-1">{children}</Label1>
</div>
)
export default InfoMessage

View file

@ -0,0 +1,21 @@
import React, { memo } from 'react'
import { Label2, P } from 'src/components/typography/index.jsx'
import Switch from '@mui/material/Switch'
const SwitchRow = memo(({ title, disabled = false, checked, save }) => {
return (
<div className="flex justify-between w-99">
<P>{title}</P>
<div className="flex items-center">
<Switch
disabled={disabled}
checked={checked}
onChange={event => save && save(event.target.checked)}
/>
<Label2>{checked ? 'Yes' : 'No'}</Label2>
</div>
</div>
)
})
export default SwitchRow

View file

@ -1,5 +1,3 @@
import Grid from '@mui/material/Grid'
import { makeStyles } from '@mui/styles'
import classnames from 'classnames'
import { Formik, Form, FastField } from 'formik'
import * as R from 'ramda'
@ -8,29 +6,7 @@ import ErrorMessage from 'src/components/ErrorMessage'
import { Button } from 'src/components/buttons'
import { SecretInput } from 'src/components/inputs/formik'
import { spacer } from 'src/styling/variables'
const styles = {
footer: {
display: 'flex',
flexDirection: 'row',
margin: [['auto', 0, spacer * 4, 0]]
},
button: {
margin: [['auto', 0, 0, 'auto']]
},
form: {
flex: 1,
display: 'flex',
flexDirection: 'column'
},
grid: {
marginBottom: 24,
marginTop: 12
}
}
const useStyles = makeStyles(styles)
const FormRenderer = ({
validationSchema,
elements,
@ -40,8 +16,6 @@ const FormRenderer = ({
buttonClass,
xs = 12
}) => {
const classes = useStyles()
const initialValues = R.compose(
R.mergeAll,
R.map(({ code }) => ({ [code]: (value && value[code]) ?? '' }))
@ -74,11 +48,11 @@ const FormRenderer = ({
validationSchema={validationSchema}
onSubmit={saveNonEmptySecret}>
{({ errors }) => (
<Form className={classes.form}>
<Grid container spacing={3} className={classes.grid}>
<Form className="flex flex-col flex-1">
<div className="flex flex-col gap-3 mb-6 mt-3">
{elements.map(
({ component, code, display, settings, inputProps }) => (
<Grid item xs={xs} key={code}>
<div key={code}>
<FastField
component={component}
{...inputProps}
@ -87,18 +61,18 @@ const FormRenderer = ({
settings={settings}
fullWidth={true}
/>
</Grid>
</div>
)
)}
</Grid>
<div className={classes.footer}>
</div>
<div className="flex flex-row mt-auto mb-8">
{!R.isEmpty(R.mergeRight(errors, saveError)) && (
<ErrorMessage>
{R.head(R.values(R.mergeRight(errors, saveError)))}
</ErrorMessage>
)}
<Button
className={classnames(classes.button, buttonClass)}
className={classnames('mt-auto ml-auto', buttonClass)}
type="submit">
{buttonLabel}
</Button>

View file

@ -1,6 +1,4 @@
import { useQuery, useMutation, gql } from "@apollo/client";
import Grid from '@mui/material/Grid'
import { makeStyles } from '@mui/styles'
import { useQuery, useMutation, gql } from '@apollo/client'
import * as R from 'ramda'
import React, { useState } from 'react'
import Modal from 'src/components/Modal'
@ -33,16 +31,6 @@ const SAVE_ACCOUNT = gql`
}
`
const styles = {
wrapper: {
// widths + spacing is a little over 1200 on the design
// this adjusts the margin after a small reduction on card size
marginLeft: 1
}
}
const useStyles = makeStyles(styles)
const Services = () => {
const [editingSchema, setEditingSchema] = useState(null)
@ -57,8 +45,6 @@ const Services = () => {
const schemas = _schemas(markets)
const classes = useStyles()
const accounts = data?.accounts ?? {}
const getItems = (code, elements) => {
@ -116,20 +102,19 @@ const Services = () => {
return (
!loading && (
<div className={classes.wrapper}>
<div>
<TitleSection title="Third-Party services" />
<Grid container spacing={4}>
<div className="grid grid-cols-3 gap-5">
{R.values(schemas).map(schema => (
<Grid item key={schema.code}>
<SingleRowTable
editMessage={'Configure ' + schema.title}
title={schema.title}
onEdit={() => setEditingSchema(schema)}
items={getItems(schema.code, schema.elements)}
/>
</Grid>
<SingleRowTable
key={schema.code}
editMessage={'Configure ' + schema.title}
title={schema.title}
onEdit={() => setEditingSchema(schema)}
items={getItems(schema.code, schema.elements)}
/>
))}
</Grid>
</div>
{editingSchema && (
<Modal
title={`Edit ${editingSchema.name}`}