feat: use Namespaced table and wizard on Cashout
This commit is contained in:
parent
af95a366c6
commit
1cf4168294
9 changed files with 337 additions and 559 deletions
|
|
@ -1,23 +1,24 @@
|
||||||
import { useQuery, useMutation } from '@apollo/react-hooks'
|
import { useQuery, useMutation } from '@apollo/react-hooks'
|
||||||
import { makeStyles } from '@material-ui/core'
|
|
||||||
import { gql } from 'apollo-boost'
|
import { gql } from 'apollo-boost'
|
||||||
|
import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
import Modal from 'src/components/Modal'
|
import { NamespacedTable as EditableTable } from 'src/components/editableTable'
|
||||||
import Title from 'src/components/Title'
|
import TitleSection from 'src/components/layout/TitleSection'
|
||||||
import { Switch } from 'src/components/inputs'
|
import { fromNamespace, toNamespace } from 'src/utils/config'
|
||||||
import { P, Info2 } from 'src/components/typography'
|
|
||||||
import { mainStyles } from 'src/pages/Transactions/Transactions.styles'
|
|
||||||
import commonStyles from 'src/pages/common.styles'
|
|
||||||
import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg'
|
|
||||||
import { spacer, zircon } from 'src/styling/variables'
|
|
||||||
|
|
||||||
import Table from './CashoutTable'
|
|
||||||
import Wizard from './Wizard'
|
import Wizard from './Wizard'
|
||||||
import WizardSplash from './WizardSplash'
|
import { DenominationsSchema, getElements } from './helper'
|
||||||
|
|
||||||
const GET_MACHINES_AND_CONFIG = gql`
|
const SAVE_CONFIG = gql`
|
||||||
{
|
mutation Save($config: JSONObject, $accounts: [JSONObject]) {
|
||||||
|
saveConfig(config: $config)
|
||||||
|
saveAccounts(accounts: $accounts)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const GET_INFO = gql`
|
||||||
|
query getData {
|
||||||
machines {
|
machines {
|
||||||
name
|
name
|
||||||
deviceId
|
deviceId
|
||||||
|
|
@ -29,218 +30,62 @@ const GET_MACHINES_AND_CONFIG = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const SAVE_CONFIG = gql`
|
const CashOut = ({ name: SCREEN_KEY }) => {
|
||||||
mutation Save($config: JSONObject) {
|
const [wizard, setWizard] = useState(false)
|
||||||
saveConfig(config: $config)
|
const [error, setError] = useState(false)
|
||||||
}
|
const { data } = useQuery(GET_INFO)
|
||||||
`
|
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const [saveConfig] = useMutation(SAVE_CONFIG, {
|
||||||
...mainStyles,
|
onCompleted: () => setWizard(false),
|
||||||
commonStyles,
|
onError: () => setError(true),
|
||||||
help: {
|
refetchQueries: () => ['getData']
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
marginLeft: spacer * 2
|
|
||||||
},
|
|
||||||
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 Cashboxes = () => {
|
|
||||||
const [machines, setMachines] = useState([])
|
|
||||||
const [config, setConfig] = useState({})
|
|
||||||
|
|
||||||
const [modalContent, setModalContent] = useState(null)
|
|
||||||
const [modalOpen, setModalOpen] = useState(false)
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const { refetch } = useQuery(GET_MACHINES_AND_CONFIG, {
|
|
||||||
onCompleted: ({ machines, config }) => {
|
|
||||||
setMachines(
|
|
||||||
machines.map(m => ({
|
|
||||||
...m,
|
|
||||||
currency: config.fiatCurrency ?? { code: 'N/D' },
|
|
||||||
cashOutDenominations: (config.cashOutDenominations ?? {})[m.deviceId]
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
setConfig(config)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const [saveConfig] = useMutation(SAVE_CONFIG)
|
const save = (rawConfig, accounts) => {
|
||||||
|
const config = toNamespace(SCREEN_KEY)(rawConfig)
|
||||||
|
setError(false)
|
||||||
|
|
||||||
const saveCashoutConfig = machine =>
|
return saveConfig({ variables: { config, accounts } })
|
||||||
saveConfig({
|
|
||||||
variables: {
|
|
||||||
config: {
|
|
||||||
...config,
|
|
||||||
cashOutDenominations: {
|
|
||||||
...config.cashOutDenominations,
|
|
||||||
[machine.deviceId]: machine.cashOutDenominations
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleEnable = machine => event => {
|
|
||||||
setModalContent(
|
|
||||||
<WizardSplash
|
|
||||||
handleModalNavigation={handleModalNavigation(machine)}
|
|
||||||
machine={machine}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
setModalOpen(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditClick = row => {
|
const config = data?.config && fromNamespace(SCREEN_KEY)(data.config)
|
||||||
setModalOpen(true)
|
const locale = data?.config && fromNamespace('locale')(data.config)
|
||||||
handleModalNavigation(row)(1)
|
const machines = data?.machines ?? []
|
||||||
|
|
||||||
|
const onToggle = id => {
|
||||||
|
const namespaced = fromNamespace(id)(config)
|
||||||
|
if (!DenominationsSchema.isValidSync(namespaced)) return setWizard(id)
|
||||||
|
save(toNamespace(id, { active: !namespaced?.active }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleModalClose = () => {
|
|
||||||
setModalOpen(false)
|
|
||||||
setModalContent(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleModalNavigation = machine => currentPage => {
|
|
||||||
switch (currentPage) {
|
|
||||||
case 1:
|
|
||||||
setModalContent(
|
|
||||||
<Wizard
|
|
||||||
handleModalNavigation={handleModalNavigation}
|
|
||||||
pageName="Edit Cassette 1 (Top)"
|
|
||||||
machine={machine}
|
|
||||||
currentStage={1}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
setModalContent(
|
|
||||||
<Wizard
|
|
||||||
machine={machine}
|
|
||||||
handleModalNavigation={handleModalNavigation}
|
|
||||||
pageName="Edit Cassette 2"
|
|
||||||
currentStage={2}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
setModalContent(
|
|
||||||
<Wizard
|
|
||||||
machine={machine}
|
|
||||||
handleModalNavigation={handleModalNavigation}
|
|
||||||
pageName="Cashout Bill Count"
|
|
||||||
currentStage={3}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
// save
|
|
||||||
return saveCashoutConfig(machine)
|
|
||||||
.then(refetch)
|
|
||||||
.then(() => {
|
|
||||||
setModalOpen(false)
|
|
||||||
setModalContent(null)
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
const elements = [
|
|
||||||
{
|
|
||||||
header: 'Machine',
|
|
||||||
size: 254,
|
|
||||||
textAlign: 'left',
|
|
||||||
view: m => m.name
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Cassette 1 (Top)',
|
|
||||||
size: 265,
|
|
||||||
textAlign: 'left',
|
|
||||||
view: ({ cashOutDenominations, currency }) => (
|
|
||||||
<>
|
|
||||||
{cashOutDenominations && cashOutDenominations.top && (
|
|
||||||
<Info2>
|
|
||||||
{cashOutDenominations.top} {currency.code}
|
|
||||||
</Info2>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Cassette 2',
|
|
||||||
size: 265,
|
|
||||||
textAlign: 'left',
|
|
||||||
view: ({ cashOutDenominations, currency }) => (
|
|
||||||
<>
|
|
||||||
{cashOutDenominations && cashOutDenominations.bottom && (
|
|
||||||
<Info2>
|
|
||||||
{cashOutDenominations.bottom} {currency.code}
|
|
||||||
</Info2>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Edit',
|
|
||||||
size: 265,
|
|
||||||
textAlign: 'left'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
header: 'Enable',
|
|
||||||
size: 151,
|
|
||||||
textAlign: 'right'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={classes.titleWrapper}>
|
<TitleSection title="Cash-out" error={error} />
|
||||||
<div className={classes.titleAndButtonsContainer}>
|
<EditableTable
|
||||||
<Title>Cash-out</Title>
|
name="test"
|
||||||
</div>
|
namespaces={R.map(R.path(['deviceId']))(machines)}
|
||||||
<div>
|
data={config}
|
||||||
<P>
|
stripeWhen={it => !DenominationsSchema.isValidSync(it)}
|
||||||
Transaction fudge factor <Switch checked={true} /> On{' '}
|
enableEdit
|
||||||
<HelpIcon className={classes.help} />
|
editWidth={134}
|
||||||
</P>
|
enableToggle
|
||||||
</div>
|
toggleWidth={109}
|
||||||
</div>
|
onToggle={onToggle}
|
||||||
<Table
|
save={save}
|
||||||
elements={elements}
|
validationSchema={DenominationsSchema}
|
||||||
data={machines}
|
disableRowEdit={R.compose(R.not, R.path(['active']))}
|
||||||
handleEnable={handleEnable}
|
elements={getElements(machines, locale)}
|
||||||
handleEditClick={handleEditClick}
|
|
||||||
/>
|
/>
|
||||||
<Modal
|
{wizard && (
|
||||||
aria-labelledby="simple-modal-title"
|
<Wizard
|
||||||
aria-describedby="simple-modal-description"
|
machine={R.find(R.propEq('deviceId', wizard))(machines)}
|
||||||
open={modalOpen}
|
onClose={() => setWizard(false)}
|
||||||
handleClose={handleModalClose}
|
save={save}
|
||||||
className={classes.modal}>
|
error={error}
|
||||||
{modalContent}
|
/>
|
||||||
</Modal>
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Cashboxes
|
export default CashOut
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import React, { useState } from 'react'
|
|
||||||
|
|
||||||
import {
|
|
||||||
Th,
|
|
||||||
Tr,
|
|
||||||
Td,
|
|
||||||
THead,
|
|
||||||
TBody,
|
|
||||||
Table
|
|
||||||
} from 'src/components/fake-table/Table'
|
|
||||||
import { Switch } from 'src/components/inputs'
|
|
||||||
import { ReactComponent as EditIcon } from 'src/styling/icons/action/edit/enabled.svg'
|
|
||||||
import { spacer } from 'src/styling/variables'
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
expandButton: {
|
|
||||||
border: 'none',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
cursor: 'pointer',
|
|
||||||
padding: 4
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
borderRadius: 0
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
marginLeft: spacer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
|
||||||
|
|
||||||
const Row = ({
|
|
||||||
id,
|
|
||||||
elements,
|
|
||||||
data,
|
|
||||||
active,
|
|
||||||
rowAction,
|
|
||||||
onSave,
|
|
||||||
handleEnable,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tr
|
|
||||||
className={classnames(classes.row)}
|
|
||||||
error={data.error}
|
|
||||||
errorMessage={data.errorMessage}>
|
|
||||||
{elements
|
|
||||||
.slice(0, -2)
|
|
||||||
.map(
|
|
||||||
(
|
|
||||||
{ size, className, textAlign, view = it => it?.toString() },
|
|
||||||
idx
|
|
||||||
) => (
|
|
||||||
<Td
|
|
||||||
key={idx}
|
|
||||||
size={size}
|
|
||||||
className={className}
|
|
||||||
textAlign={textAlign}>
|
|
||||||
{view({ ...data, editing: active })}
|
|
||||||
</Td>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
<Td
|
|
||||||
size={elements[elements.length - 2].size}
|
|
||||||
textAlign={elements[elements.length - 2].textAlign}>
|
|
||||||
{data.cashOutDenominations && (
|
|
||||||
<button
|
|
||||||
onClick={() => rowAction(data)}
|
|
||||||
className={classes.expandButton}>
|
|
||||||
<EditIcon />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Td>
|
|
||||||
<Td
|
|
||||||
size={elements[elements.length - 1].size}
|
|
||||||
textAlign={elements[elements.length - 1].textAlign}>
|
|
||||||
<Switch
|
|
||||||
checked={data.cashOutDenominations}
|
|
||||||
onChange={handleEnable(data)}
|
|
||||||
/>
|
|
||||||
</Td>
|
|
||||||
</Tr>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* rows = [{ columns = [{ name, value, className, textAlign, size }], details, className, error, errorMessage }]
|
|
||||||
* Don't forget to include the size of the last (expand button) column!
|
|
||||||
*/
|
|
||||||
const CashOutTable = ({
|
|
||||||
elements = [],
|
|
||||||
data = [],
|
|
||||||
Details,
|
|
||||||
className,
|
|
||||||
onSave,
|
|
||||||
handleEditClick,
|
|
||||||
handleEnable,
|
|
||||||
...props
|
|
||||||
}) => {
|
|
||||||
const [active] = useState(null)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table>
|
|
||||||
<THead>
|
|
||||||
{elements.map(({ size, className, textAlign, header }, idx) => (
|
|
||||||
<Th key={idx} size={size} className={className} textAlign={textAlign}>
|
|
||||||
{header}
|
|
||||||
</Th>
|
|
||||||
))}
|
|
||||||
</THead>
|
|
||||||
|
|
||||||
<TBody>
|
|
||||||
{data.map((it, idx) => (
|
|
||||||
<Row
|
|
||||||
id={idx}
|
|
||||||
elements={elements}
|
|
||||||
data={it}
|
|
||||||
active={idx === active}
|
|
||||||
rowAction={handleEditClick}
|
|
||||||
onSave={onSave}
|
|
||||||
handleEnable={handleEnable}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</TBody>
|
|
||||||
</Table>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CashOutTable
|
|
||||||
|
|
@ -1,191 +1,70 @@
|
||||||
|
import * as R from 'ramda'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { makeStyles } from '@material-ui/core'
|
|
||||||
|
|
||||||
import { H1, Info2, H4, P } from 'src/components/typography'
|
import Modal from 'src/components/Modal'
|
||||||
import { Button } from 'src/components/buttons'
|
import { toNamespace } from 'src/utils/config'
|
||||||
import Stage from 'src/components/Stage'
|
|
||||||
import { TextInput } from 'src/components/inputs'
|
|
||||||
import ErrorMessage from 'src/components/ErrorMessage'
|
|
||||||
import { spacer } from 'src/styling/variables'
|
|
||||||
|
|
||||||
const styles = {
|
import WizardSplash from './WizardSplash'
|
||||||
modalContent: {
|
import WizardStep from './WizardStep'
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
const LAST_STEP = 3
|
||||||
padding: [[24, 32, 0]],
|
const MODAL_WIDTH = 554
|
||||||
'& > h1': {
|
|
||||||
margin: [[0, 0, 10]]
|
const Wizard = ({ machine, onClose, save, error }) => {
|
||||||
},
|
const [{ step, config }, setState] = useState({
|
||||||
'& > h4': {
|
step: 0,
|
||||||
margin: [[32, 0, 32 - 9, 0]]
|
config: { active: true }
|
||||||
},
|
})
|
||||||
'& > p': {
|
|
||||||
margin: 0
|
const title = `Enable cash-out`
|
||||||
|
const isLastStep = step === LAST_STEP
|
||||||
|
|
||||||
|
const onContinue = async it => {
|
||||||
|
const newConfig = R.merge(config, it)
|
||||||
|
|
||||||
|
if (isLastStep) {
|
||||||
|
return save(toNamespace(machine.deviceId, newConfig))
|
||||||
}
|
}
|
||||||
},
|
|
||||||
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
|
|
||||||
},
|
|
||||||
texInput: {
|
|
||||||
width: spacer * 6,
|
|
||||||
marginRight: spacer * 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
setState({
|
||||||
|
step: step + 1,
|
||||||
const SubmitButton = ({ error, label, ...props }) => {
|
config: newConfig
|
||||||
const classes = useStyles()
|
})
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classes.submitButtonWrapper}>
|
|
||||||
{error && <ErrorMessage>Failed to save</ErrorMessage>}
|
|
||||||
<Button {...props}>{label}</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Wizard = ({ pageName, currentStage, handleModalNavigation, machine }) => {
|
|
||||||
const [topOverride, setTopOverride] = useState(
|
|
||||||
machine?.cashOutDenominations?.top
|
|
||||||
)
|
|
||||||
const [bottomOverride, setBottomOverride] = useState(
|
|
||||||
machine?.cashOutDenominations?.bottom
|
|
||||||
)
|
|
||||||
|
|
||||||
const overrideTop = event => {
|
|
||||||
setTopOverride(Number(event.target.value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const overrideBottom = event => {
|
const getStepData = () => {
|
||||||
setBottomOverride(Number(event.target.value))
|
switch (step) {
|
||||||
}
|
|
||||||
|
|
||||||
const [error, setError] = useState(null)
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
const handleNext = machine => event => {
|
|
||||||
const cashOutDenominations = { top: topOverride, bottom: bottomOverride }
|
|
||||||
const nav = handleModalNavigation({ ...machine, cashOutDenominations })(
|
|
||||||
currentStage + 1
|
|
||||||
)
|
|
||||||
nav.catch(error => setError(error))
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSubmittable = currentStage => {
|
|
||||||
switch (currentStage) {
|
|
||||||
case 1:
|
case 1:
|
||||||
return topOverride > 0
|
return { type: 'top', display: 'Cassete 1 (Top)' }
|
||||||
case 2:
|
case 2:
|
||||||
return bottomOverride > 0
|
return { type: 'bottom', display: 'Cassete 2' }
|
||||||
|
case 3:
|
||||||
|
return { type: 'agreed' }
|
||||||
default:
|
default:
|
||||||
return isSubmittable(1) && isSubmittable(2)
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.modalContent}>
|
<Modal
|
||||||
<H1>Enable cash-out</H1>
|
title={step === 0 ? null : title}
|
||||||
<Info2>{machine.name}</Info2>
|
handleClose={onClose}
|
||||||
<Stage
|
width={MODAL_WIDTH}
|
||||||
stages={3}
|
open={true}>
|
||||||
currentStage={currentStage}
|
{step === 0 && (
|
||||||
color="spring"
|
<WizardSplash name={machine.name} onContinue={() => onContinue()} />
|
||||||
className={classes.stages}
|
|
||||||
/>
|
|
||||||
{currentStage < 3 && (
|
|
||||||
<>
|
|
||||||
<H4>{pageName}</H4>
|
|
||||||
<P>Choose bill denomination</P>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
<div>
|
{step !== 0 && (
|
||||||
{currentStage < 3 && (
|
<WizardStep
|
||||||
<>
|
step={step}
|
||||||
{currentStage === 1 && (
|
name={machine.name}
|
||||||
<TextInput
|
error={error}
|
||||||
autoFocus
|
lastStep={isLastStep}
|
||||||
id="confirm-input"
|
{...getStepData()}
|
||||||
type="text"
|
onContinue={onContinue}
|
||||||
large
|
/>
|
||||||
value={topOverride}
|
)}
|
||||||
touched={{}}
|
</Modal>
|
||||||
error={false}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
onChange={overrideTop}
|
|
||||||
className={classes.texInput}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentStage === 2 && (
|
|
||||||
<TextInput
|
|
||||||
autoFocus
|
|
||||||
id="confirm-input"
|
|
||||||
type="text"
|
|
||||||
large
|
|
||||||
value={bottomOverride}
|
|
||||||
touched={{}}
|
|
||||||
error={false}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
onChange={overrideBottom}
|
|
||||||
className={classes.texInput}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
disabled
|
|
||||||
autoFocus
|
|
||||||
id="confirm-input"
|
|
||||||
type="text"
|
|
||||||
large
|
|
||||||
value={machine.currency.code}
|
|
||||||
touched={{}}
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
className={classes.texInput}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{currentStage === 3 && (
|
|
||||||
<>
|
|
||||||
<H4>{pageName}</H4>
|
|
||||||
<P>
|
|
||||||
When enabling cash out, your bill count will be authomatically set
|
|
||||||
to zero. Make sure you physically put cash inside the cashboxes to
|
|
||||||
allow the machine to dispense it to your users. If you already
|
|
||||||
did, make sure you set the correct cash out bill count for this
|
|
||||||
machine on your Cashboxes tab under Maintenance.
|
|
||||||
</P>
|
|
||||||
<H4>{pageName}</H4>
|
|
||||||
<P>
|
|
||||||
When enabling cash out, default commissions will be set. To change
|
|
||||||
commissions for this machine, please go to the Commissions tab
|
|
||||||
under Settings. where you can set exceptions for each of the
|
|
||||||
available cryptocurrencies.
|
|
||||||
</P>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<SubmitButton
|
|
||||||
className={classes.submitButton}
|
|
||||||
label={currentStage === 3 ? 'Finish' : 'Next'}
|
|
||||||
disabled={!isSubmittable(currentStage)}
|
|
||||||
onClick={handleNext(machine)}
|
|
||||||
error={error}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,49 @@
|
||||||
import React from 'react'
|
|
||||||
import { makeStyles } from '@material-ui/core'
|
import { makeStyles } from '@material-ui/core'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
import { H1, P } from 'src/components/typography'
|
|
||||||
import { Button } from 'src/components/buttons'
|
import { Button } from 'src/components/buttons'
|
||||||
import { neon, spacer } from 'src/styling/variables'
|
import { H1, P } from 'src/components/typography'
|
||||||
|
|
||||||
const styles = {
|
const styles = {
|
||||||
logoWrapper: {
|
logo: {
|
||||||
display: 'flex',
|
maxHeight: 80,
|
||||||
justifyContent: 'center',
|
maxWidth: 200
|
||||||
alignItems: 'center',
|
},
|
||||||
height: 80,
|
title: {
|
||||||
margin: [[40, 0, 24]],
|
margin: [[24, 0, 32, 0]]
|
||||||
'& > svg': {
|
},
|
||||||
maxHeight: '100%',
|
text: {
|
||||||
width: '100%'
|
margin: 0
|
||||||
}
|
},
|
||||||
|
button: {
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 58
|
||||||
},
|
},
|
||||||
modalContent: {
|
modalContent: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: [[0, 66]],
|
padding: [[0, 42]],
|
||||||
'& > h1': {
|
flex: 1
|
||||||
color: neon,
|
|
||||||
margin: [[spacer * 8, 0, 32]]
|
|
||||||
},
|
|
||||||
'& > p': {
|
|
||||||
margin: 0
|
|
||||||
},
|
|
||||||
'& > button': {
|
|
||||||
margin: [['auto', 0, 56]],
|
|
||||||
'&:active': {
|
|
||||||
margin: [['auto', 0, 56]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const WizardSplash = ({ handleModalNavigation, machine }) => {
|
const WizardSplash = ({ name, onContinue }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.modalContent}>
|
<div className={classes.modalContent}>
|
||||||
<H1>Enable cash-out</H1>
|
<H1 className={classes.title}>Enable cash-out</H1>
|
||||||
<P>
|
<P className={classes.text}>
|
||||||
You are about to activate cash-out functionality on your {machine.name}{' '}
|
You are about to activate cash-out functionality on your {name} machine
|
||||||
machine which will allow your customers to sell crypto to you.
|
which will allow your customers to sell crypto to you.
|
||||||
<br />
|
|
||||||
<br />
|
<br />
|
||||||
In order to activate cash-out for this machine, please enter the
|
In order to activate cash-out for this machine, please enter the
|
||||||
denominations for the machine.
|
denominations for the machine.
|
||||||
</P>
|
</P>
|
||||||
<Button onClick={() => handleModalNavigation(1)}>
|
<Button className={classes.button} onClick={onContinue}>
|
||||||
Start configuration
|
Start configuration
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
130
new-lamassu-admin/src/pages/Cashout/WizardStep.js
Normal file
130
new-lamassu-admin/src/pages/Cashout/WizardStep.js
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
import { makeStyles } from '@material-ui/core'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import * as R from 'ramda'
|
||||||
|
import React, { useReducer, useEffect } from 'react'
|
||||||
|
|
||||||
|
import ErrorMessage from 'src/components/ErrorMessage'
|
||||||
|
import Stepper from 'src/components/Stepper'
|
||||||
|
import { Button } from 'src/components/buttons'
|
||||||
|
import { TextInput } from 'src/components/inputs'
|
||||||
|
import { Info2, H4, P } from 'src/components/typography'
|
||||||
|
|
||||||
|
import styles from './WizardStep.styles'
|
||||||
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
selected: null,
|
||||||
|
iError: false
|
||||||
|
}
|
||||||
|
|
||||||
|
const reducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'select':
|
||||||
|
return {
|
||||||
|
form: null,
|
||||||
|
selected: action.selected,
|
||||||
|
isNew: null,
|
||||||
|
iError: false
|
||||||
|
}
|
||||||
|
case 'form':
|
||||||
|
return {
|
||||||
|
form: action.form,
|
||||||
|
selected: action.form.code,
|
||||||
|
isNew: true,
|
||||||
|
iError: false
|
||||||
|
}
|
||||||
|
case 'error':
|
||||||
|
return R.merge(state, { iError: true })
|
||||||
|
case 'reset':
|
||||||
|
return initialState
|
||||||
|
default:
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WizardStep = ({
|
||||||
|
type,
|
||||||
|
name,
|
||||||
|
step,
|
||||||
|
error,
|
||||||
|
lastStep,
|
||||||
|
onContinue,
|
||||||
|
display
|
||||||
|
}) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const [{ iError, selected }, dispatch] = useReducer(reducer, initialState)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch({ type: 'reset' })
|
||||||
|
}, [step])
|
||||||
|
|
||||||
|
const iContinue = config => {
|
||||||
|
if (lastStep) config[type] = true
|
||||||
|
|
||||||
|
if (!config || !config[type]) {
|
||||||
|
return dispatch({ type: 'error' })
|
||||||
|
}
|
||||||
|
|
||||||
|
onContinue(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = lastStep ? 'Finish' : 'Next'
|
||||||
|
const subtitleClass = {
|
||||||
|
[classes.subtitle]: true,
|
||||||
|
[classes.error]: iError
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Info2 className={classes.title}>{name}</Info2>
|
||||||
|
<Stepper steps={3} currentStep={step} />
|
||||||
|
{display && <H4 className={classnames(subtitleClass)}>Edit {display}</H4>}
|
||||||
|
|
||||||
|
{!lastStep && (
|
||||||
|
<TextInput
|
||||||
|
label={'Choose bill denomination'}
|
||||||
|
onChange={evt =>
|
||||||
|
dispatch({ type: 'select', selected: evt.target.value })
|
||||||
|
}
|
||||||
|
autoFocus
|
||||||
|
id="confirm-input"
|
||||||
|
type="text"
|
||||||
|
size="lg"
|
||||||
|
touched={{}}
|
||||||
|
error={false}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
// TODO: there was a disabled link here showing the currency code; restore it
|
||||||
|
)}
|
||||||
|
|
||||||
|
{lastStep && (
|
||||||
|
<>
|
||||||
|
<P>
|
||||||
|
When enabling cash out, your bill count will be authomatically set
|
||||||
|
to zero. Make sure you physically put cash inside the cashboxes to
|
||||||
|
allow the machine to dispense it to your users. If you already did,
|
||||||
|
make sure you set the correct cash out bill count for this machine
|
||||||
|
on your Cashboxes tab under Maintenance.
|
||||||
|
</P>
|
||||||
|
<P>
|
||||||
|
When enabling cash out, default commissions will be set. To change
|
||||||
|
commissions for this machine, please go to the Commissions tab under
|
||||||
|
Settings. where you can set exceptions for each of the available
|
||||||
|
cryptocurrencies.
|
||||||
|
</P>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={classes.submit}>
|
||||||
|
{error && <ErrorMessage>Failed to save</ErrorMessage>}
|
||||||
|
<Button
|
||||||
|
className={classes.button}
|
||||||
|
onClick={() => iContinue({ [type]: selected })}>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WizardStep
|
||||||
26
new-lamassu-admin/src/pages/Cashout/WizardStep.styles.js
Normal file
26
new-lamassu-admin/src/pages/Cashout/WizardStep.styles.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { errorColor } from 'src/styling/variables'
|
||||||
|
|
||||||
|
const LABEL_WIDTH = 150
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: {
|
||||||
|
margin: [[0, 0, 12, 0]]
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
margin: [[32, 0, 21, 0]]
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
color: errorColor
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
marginLeft: 'auto'
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
margin: [['auto', 0, 24]]
|
||||||
|
},
|
||||||
|
picker: {
|
||||||
|
width: LABEL_WIDTH
|
||||||
|
}
|
||||||
|
}
|
||||||
41
new-lamassu-admin/src/pages/Cashout/helper.js
Normal file
41
new-lamassu-admin/src/pages/Cashout/helper.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
import TextInput from 'src/components/inputs/formik/TextInput'
|
||||||
|
|
||||||
|
const DenominationsSchema = Yup.object().shape({
|
||||||
|
top: Yup.number().required('Required'),
|
||||||
|
bottom: Yup.number().required('Required')
|
||||||
|
})
|
||||||
|
|
||||||
|
const getElements = (machines, { fiatCurrency } = {}) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
header: 'Machine',
|
||||||
|
width: 254,
|
||||||
|
view: it => machines.find(({ deviceId }) => deviceId === it).name,
|
||||||
|
size: 'sm',
|
||||||
|
editable: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'top',
|
||||||
|
header: 'Cassette 1 (Top)',
|
||||||
|
view: it => `${it} ${fiatCurrency}`,
|
||||||
|
size: 'sm',
|
||||||
|
stripe: true,
|
||||||
|
width: 265,
|
||||||
|
input: TextInput
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bottom',
|
||||||
|
header: 'Cassette 2',
|
||||||
|
view: it => `${it} ${fiatCurrency}`,
|
||||||
|
size: 'sm',
|
||||||
|
stripe: true,
|
||||||
|
width: 265,
|
||||||
|
input: TextInput
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DenominationsSchema, getElements }
|
||||||
|
|
@ -68,12 +68,6 @@ const tree = [
|
||||||
return () => <Redirect to={this.children[0].route} />
|
return () => <Redirect to={this.children[0].route} />
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
key: namespaces.CASH_OUT,
|
|
||||||
label: 'Cash-out',
|
|
||||||
route: '/settings/cash-out',
|
|
||||||
component: Cashout
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: namespaces.COMMISSIONS,
|
key: namespaces.COMMISSIONS,
|
||||||
label: 'Commissions',
|
label: 'Commissions',
|
||||||
|
|
@ -86,6 +80,12 @@ const tree = [
|
||||||
route: '/settings/locale',
|
route: '/settings/locale',
|
||||||
component: Locales
|
component: Locales
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: namespaces.CASH_OUT,
|
||||||
|
label: 'Cash-out',
|
||||||
|
route: '/settings/cash-out',
|
||||||
|
component: Cashout
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: namespaces.SERVICES,
|
key: namespaces.SERVICES,
|
||||||
label: '3rd party services',
|
label: '3rd party services',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as R from 'ramda'
|
import * as R from 'ramda'
|
||||||
|
|
||||||
const namespaces = {
|
const namespaces = {
|
||||||
CASH_OUT: 'cash-out',
|
CASH_OUT: 'denominations',
|
||||||
WALLETS: 'wallets',
|
WALLETS: 'wallets',
|
||||||
OPERATOR_INFO: 'operatorInfo',
|
OPERATOR_INFO: 'operatorInfo',
|
||||||
NOTIFICATIONS: 'notifications',
|
NOTIFICATIONS: 'notifications',
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue