diff --git a/new-lamassu-admin/src/pages/Cashout/Cashout.js b/new-lamassu-admin/src/pages/Cashout/Cashout.js
index 331e6642..5a82d083 100644
--- a/new-lamassu-admin/src/pages/Cashout/Cashout.js
+++ b/new-lamassu-admin/src/pages/Cashout/Cashout.js
@@ -1,23 +1,24 @@
import { useQuery, useMutation } from '@apollo/react-hooks'
-import { makeStyles } from '@material-ui/core'
import { gql } from 'apollo-boost'
+import * as R from 'ramda'
import React, { useState } from 'react'
-import Modal from 'src/components/Modal'
-import Title from 'src/components/Title'
-import { Switch } from 'src/components/inputs'
-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 { NamespacedTable as EditableTable } from 'src/components/editableTable'
+import TitleSection from 'src/components/layout/TitleSection'
+import { fromNamespace, toNamespace } from 'src/utils/config'
-import Table from './CashoutTable'
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 {
name
deviceId
@@ -29,218 +30,62 @@ const GET_MACHINES_AND_CONFIG = gql`
}
`
-const SAVE_CONFIG = gql`
- mutation Save($config: JSONObject) {
- saveConfig(config: $config)
- }
-`
+const CashOut = ({ name: SCREEN_KEY }) => {
+ const [wizard, setWizard] = useState(false)
+ const [error, setError] = useState(false)
+ const { data } = useQuery(GET_INFO)
-const useStyles = makeStyles({
- ...mainStyles,
- commonStyles,
- help: {
- 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, {
+ onCompleted: () => setWizard(false),
+ onError: () => setError(true),
+ refetchQueries: () => ['getData']
})
- const [saveConfig] = useMutation(SAVE_CONFIG)
+ const save = (rawConfig, accounts) => {
+ const config = toNamespace(SCREEN_KEY)(rawConfig)
+ setError(false)
- const saveCashoutConfig = machine =>
- saveConfig({
- variables: {
- config: {
- ...config,
- cashOutDenominations: {
- ...config.cashOutDenominations,
- [machine.deviceId]: machine.cashOutDenominations
- }
- }
- }
- })
-
- const handleEnable = machine => event => {
- setModalContent(
-
- )
- setModalOpen(true)
+ return saveConfig({ variables: { config, accounts } })
}
- const handleEditClick = row => {
- setModalOpen(true)
- handleModalNavigation(row)(1)
+ const config = data?.config && fromNamespace(SCREEN_KEY)(data.config)
+ const locale = data?.config && fromNamespace('locale')(data.config)
+ 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(
-
- )
- break
- case 2:
- setModalContent(
-
- )
- break
- case 3:
- setModalContent(
-
- )
- 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 && (
-
- {cashOutDenominations.top} {currency.code}
-
- )}
- >
- )
- },
- {
- header: 'Cassette 2',
- size: 265,
- textAlign: 'left',
- view: ({ cashOutDenominations, currency }) => (
- <>
- {cashOutDenominations && cashOutDenominations.bottom && (
-
- {cashOutDenominations.bottom} {currency.code}
-
- )}
- >
- )
- },
- {
- header: 'Edit',
- size: 265,
- textAlign: 'left'
- },
- {
- header: 'Enable',
- size: 151,
- textAlign: 'right'
- }
- ]
-
return (
<>
-
-
-
Cash-out
-
-
-
- Transaction fudge factor On{' '}
-
-
-
-
-
+ !DenominationsSchema.isValidSync(it)}
+ enableEdit
+ editWidth={134}
+ enableToggle
+ toggleWidth={109}
+ onToggle={onToggle}
+ save={save}
+ validationSchema={DenominationsSchema}
+ disableRowEdit={R.compose(R.not, R.path(['active']))}
+ elements={getElements(machines, locale)}
/>
-
- {modalContent}
-
+ {wizard && (
+ setWizard(false)}
+ save={save}
+ error={error}
+ />
+ )}
>
)
}
-export default Cashboxes
+export default CashOut
diff --git a/new-lamassu-admin/src/pages/Cashout/CashoutTable.js b/new-lamassu-admin/src/pages/Cashout/CashoutTable.js
deleted file mode 100644
index b5b607c3..00000000
--- a/new-lamassu-admin/src/pages/Cashout/CashoutTable.js
+++ /dev/null
@@ -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 (
-
- {elements
- .slice(0, -2)
- .map(
- (
- { size, className, textAlign, view = it => it?.toString() },
- idx
- ) => (
-
- {view({ ...data, editing: active })}
-
- )
- )}
-
- {data.cashOutDenominations && (
- rowAction(data)}
- className={classes.expandButton}>
-
-
- )}
-
-
-
-
-
- )
-}
-
-/* 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 (
-
-
- {elements.map(({ size, className, textAlign, header }, idx) => (
-
- {header}
-
- ))}
-
-
-
- {data.map((it, idx) => (
-
- ))}
-
-
- )
-}
-
-export default CashOutTable
diff --git a/new-lamassu-admin/src/pages/Cashout/Wizard.js b/new-lamassu-admin/src/pages/Cashout/Wizard.js
index 909eac9c..a49576b3 100644
--- a/new-lamassu-admin/src/pages/Cashout/Wizard.js
+++ b/new-lamassu-admin/src/pages/Cashout/Wizard.js
@@ -1,191 +1,70 @@
+import * as R from 'ramda'
import React, { useState } from 'react'
-import { makeStyles } from '@material-ui/core'
-import { H1, Info2, H4, P } from 'src/components/typography'
-import { Button } from 'src/components/buttons'
-import Stage from 'src/components/Stage'
-import { TextInput } from 'src/components/inputs'
-import ErrorMessage from 'src/components/ErrorMessage'
-import { spacer } from 'src/styling/variables'
+import Modal from 'src/components/Modal'
+import { toNamespace } from 'src/utils/config'
-const styles = {
- modalContent: {
- display: 'flex',
- flexDirection: 'column',
- padding: [[24, 32, 0]],
- '& > h1': {
- margin: [[0, 0, 10]]
- },
- '& > h4': {
- margin: [[32, 0, 32 - 9, 0]]
- },
- '& > p': {
- margin: 0
+import WizardSplash from './WizardSplash'
+import WizardStep from './WizardStep'
+
+const LAST_STEP = 3
+const MODAL_WIDTH = 554
+
+const Wizard = ({ machine, onClose, save, error }) => {
+ const [{ step, config }, setState] = useState({
+ step: 0,
+ config: { active: true }
+ })
+
+ 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)
-
-const SubmitButton = ({ error, label, ...props }) => {
- const classes = useStyles()
-
- return (
-
- {error && Failed to save }
- {label}
-
- )
-}
-
-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))
+ setState({
+ step: step + 1,
+ config: newConfig
+ })
}
- const overrideBottom = event => {
- setBottomOverride(Number(event.target.value))
- }
-
- 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) {
+ const getStepData = () => {
+ switch (step) {
case 1:
- return topOverride > 0
+ return { type: 'top', display: 'Cassete 1 (Top)' }
case 2:
- return bottomOverride > 0
+ return { type: 'bottom', display: 'Cassete 2' }
+ case 3:
+ return { type: 'agreed' }
default:
- return isSubmittable(1) && isSubmittable(2)
+ return null
}
}
return (
-
-
Enable cash-out
-
{machine.name}
-
- {currentStage < 3 && (
- <>
-
{pageName}
-
Choose bill denomination
- >
+
+ {step === 0 && (
+ onContinue()} />
)}
-
- {currentStage < 3 && (
- <>
- {currentStage === 1 && (
-
- )}
-
- {currentStage === 2 && (
-
- )}
-
-
- >
- )}
- {currentStage === 3 && (
- <>
-
{pageName}
-
- 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.
-
-
{pageName}
-
- 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.
-
- >
- )}
-
-
-
+ {step !== 0 && (
+
+ )}
+
)
}
diff --git a/new-lamassu-admin/src/pages/Cashout/WizardSplash.js b/new-lamassu-admin/src/pages/Cashout/WizardSplash.js
index d1c2d42e..1a410bab 100644
--- a/new-lamassu-admin/src/pages/Cashout/WizardSplash.js
+++ b/new-lamassu-admin/src/pages/Cashout/WizardSplash.js
@@ -1,60 +1,49 @@
-import React from 'react'
import { makeStyles } from '@material-ui/core'
+import React from 'react'
-import { H1, P } from 'src/components/typography'
import { Button } from 'src/components/buttons'
-import { neon, spacer } from 'src/styling/variables'
+import { H1, P } from 'src/components/typography'
const styles = {
- logoWrapper: {
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- height: 80,
- margin: [[40, 0, 24]],
- '& > svg': {
- maxHeight: '100%',
- width: '100%'
- }
+ logo: {
+ maxHeight: 80,
+ maxWidth: 200
+ },
+ title: {
+ margin: [[24, 0, 32, 0]]
+ },
+ text: {
+ margin: 0
+ },
+ button: {
+ marginTop: 'auto',
+ marginBottom: 58
},
modalContent: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
- padding: [[0, 66]],
- '& > h1': {
- color: neon,
- margin: [[spacer * 8, 0, 32]]
- },
- '& > p': {
- margin: 0
- },
- '& > button': {
- margin: [['auto', 0, 56]],
- '&:active': {
- margin: [['auto', 0, 56]]
- }
- }
+ padding: [[0, 42]],
+ flex: 1
}
}
const useStyles = makeStyles(styles)
-const WizardSplash = ({ handleModalNavigation, machine }) => {
+const WizardSplash = ({ name, onContinue }) => {
const classes = useStyles()
return (
-
Enable cash-out
-
- You are about to activate cash-out functionality on your {machine.name}{' '}
- machine which will allow your customers to sell crypto to you.
-
+
Enable cash-out
+
+ You are about to activate cash-out functionality on your {name} machine
+ which will allow your customers to sell crypto to you.
In order to activate cash-out for this machine, please enter the
denominations for the machine.
-
handleModalNavigation(1)}>
+
Start configuration
diff --git a/new-lamassu-admin/src/pages/Cashout/WizardStep.js b/new-lamassu-admin/src/pages/Cashout/WizardStep.js
new file mode 100644
index 00000000..e2fe99fa
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Cashout/WizardStep.js
@@ -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 (
+ <>
+ {name}
+
+ {display && Edit {display} }
+
+ {!lastStep && (
+
+ 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 && (
+ <>
+
+ 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.
+
+
+ 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.
+
+ >
+ )}
+
+
+ {error && Failed to save }
+ iContinue({ [type]: selected })}>
+ {label}
+
+
+ >
+ )
+}
+
+export default WizardStep
diff --git a/new-lamassu-admin/src/pages/Cashout/WizardStep.styles.js b/new-lamassu-admin/src/pages/Cashout/WizardStep.styles.js
new file mode 100644
index 00000000..d143ab4f
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Cashout/WizardStep.styles.js
@@ -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
+ }
+}
diff --git a/new-lamassu-admin/src/pages/Cashout/helper.js b/new-lamassu-admin/src/pages/Cashout/helper.js
new file mode 100644
index 00000000..466da650
--- /dev/null
+++ b/new-lamassu-admin/src/pages/Cashout/helper.js
@@ -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 }
diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js
index 4bd1ca65..90535954 100644
--- a/new-lamassu-admin/src/routing/routes.js
+++ b/new-lamassu-admin/src/routing/routes.js
@@ -68,12 +68,6 @@ const tree = [
return () =>
},
children: [
- {
- key: namespaces.CASH_OUT,
- label: 'Cash-out',
- route: '/settings/cash-out',
- component: Cashout
- },
{
key: namespaces.COMMISSIONS,
label: 'Commissions',
@@ -86,6 +80,12 @@ const tree = [
route: '/settings/locale',
component: Locales
},
+ {
+ key: namespaces.CASH_OUT,
+ label: 'Cash-out',
+ route: '/settings/cash-out',
+ component: Cashout
+ },
{
key: namespaces.SERVICES,
label: '3rd party services',
diff --git a/new-lamassu-admin/src/utils/config.js b/new-lamassu-admin/src/utils/config.js
index 4b98c793..f4f3c810 100644
--- a/new-lamassu-admin/src/utils/config.js
+++ b/new-lamassu-admin/src/utils/config.js
@@ -1,7 +1,7 @@
import * as R from 'ramda'
const namespaces = {
- CASH_OUT: 'cash-out',
+ CASH_OUT: 'denominations',
WALLETS: 'wallets',
OPERATOR_INFO: 'operatorInfo',
NOTIFICATIONS: 'notifications',