diff --git a/new-lamassu-admin/src/pages/Cashout/Cashout.js b/new-lamassu-admin/src/pages/Cashout/Cashout.js new file mode 100644 index 00000000..331e6642 --- /dev/null +++ b/new-lamassu-admin/src/pages/Cashout/Cashout.js @@ -0,0 +1,246 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import { gql } from 'apollo-boost' +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 Table from './CashoutTable' +import Wizard from './Wizard' +import WizardSplash from './WizardSplash' + +const GET_MACHINES_AND_CONFIG = gql` + { + machines { + name + deviceId + cashbox + cassette1 + cassette2 + } + config + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` + +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) + + const saveCashoutConfig = machine => + saveConfig({ + variables: { + config: { + ...config, + cashOutDenominations: { + ...config.cashOutDenominations, + [machine.deviceId]: machine.cashOutDenominations + } + } + } + }) + + const handleEnable = machine => event => { + setModalContent( + + ) + setModalOpen(true) + } + + const handleEditClick = row => { + setModalOpen(true) + handleModalNavigation(row)(1) + } + + 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{' '} + +

+
+
+ + + {modalContent} + + + ) +} + +export default Cashboxes diff --git a/new-lamassu-admin/src/pages/Cashout/CashoutTable.js b/new-lamassu-admin/src/pages/Cashout/CashoutTable.js new file mode 100644 index 00000000..b5b607c3 --- /dev/null +++ b/new-lamassu-admin/src/pages/Cashout/CashoutTable.js @@ -0,0 +1,132 @@ +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 + ) => ( + + ) + )} + + + + ) +} + +/* 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 ( +
+ {view({ ...data, editing: active })} + + {data.cashOutDenominations && ( + + )} + + +
+ + {elements.map(({ size, className, textAlign, header }, idx) => ( + + ))} + + + + {data.map((it, idx) => ( + + ))} + +
+ {header} +
+ ) +} + +export default CashOutTable diff --git a/new-lamassu-admin/src/pages/Cashout/Wizard.js b/new-lamassu-admin/src/pages/Cashout/Wizard.js new file mode 100644 index 00000000..909eac9c --- /dev/null +++ b/new-lamassu-admin/src/pages/Cashout/Wizard.js @@ -0,0 +1,192 @@ +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' + +const styles = { + modalContent: { + display: 'flex', + flexDirection: 'column', + padding: [[24, 32, 0]], + '& > h1': { + margin: [[0, 0, 10]] + }, + '& > h4': { + margin: [[32, 0, 32 - 9, 0]] + }, + '& > p': { + margin: 0 + } + }, + submitButtonWrapper: { + display: 'flex', + alignSelf: 'flex-end', + margin: [['auto', 0, 0]] + }, + submitButton: { + width: 67, + padding: [[0, 0]], + margin: [['auto', 0, 24, 20]], + '&:active': { + margin: [['auto', 0, 24, 20]] + } + }, + stages: { + marginTop: 10 + }, + texInput: { + width: spacer * 6, + marginRight: spacer * 2 + } +} + +const useStyles = makeStyles(styles) + +const SubmitButton = ({ error, label, ...props }) => { + const classes = useStyles() + + return ( +
+ {error && Failed to save} + +
+ ) +} + +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 => { + 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) { + case 1: + return topOverride > 0 + case 2: + return bottomOverride > 0 + default: + return isSubmittable(1) && isSubmittable(2) + } + } + + return ( +
+

Enable cash-out

+ {machine.name} + + {currentStage < 3 && ( + <> +

{pageName}

+

Choose bill denomination

+ + )} +
+ {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. +

+ + )} +
+ +
+ ) +} + +export default Wizard diff --git a/new-lamassu-admin/src/pages/Cashout/WizardSplash.js b/new-lamassu-admin/src/pages/Cashout/WizardSplash.js new file mode 100644 index 00000000..d1c2d42e --- /dev/null +++ b/new-lamassu-admin/src/pages/Cashout/WizardSplash.js @@ -0,0 +1,64 @@ +import React from 'react' +import { makeStyles } from '@material-ui/core' + +import { H1, P } from 'src/components/typography' +import { Button } from 'src/components/buttons' +import { neon, spacer } from 'src/styling/variables' + +const styles = { + logoWrapper: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: 80, + margin: [[40, 0, 24]], + '& > svg': { + maxHeight: '100%', + width: '100%' + } + }, + modalContent: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + padding: [[0, 66]], + '& > h1': { + 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 WizardSplash = ({ handleModalNavigation, machine }) => { + 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. +
+
+ In order to activate cash-out for this machine, please enter the + denominations for the machine. +

+ +
+ ) +} + +export default WizardSplash diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js index 6ee06e6d..4bd1ca65 100644 --- a/new-lamassu-admin/src/routing/routes.js +++ b/new-lamassu-admin/src/routing/routes.js @@ -3,6 +3,7 @@ import React from 'react' import { Route, Redirect, Switch } from 'react-router-dom' import AuthRegister from 'src/pages/AuthRegister' +import Cashout from 'src/pages/Cashout/Cashout' import Commissions from 'src/pages/Commissions' import Customers from 'src/pages/Customers' import Funding from 'src/pages/Funding' @@ -67,6 +68,12 @@ const tree = [ return () => }, children: [ + { + key: namespaces.CASH_OUT, + label: 'Cash-out', + route: '/settings/cash-out', + component: Cashout + }, { key: namespaces.COMMISSIONS, label: 'Commissions', diff --git a/new-lamassu-admin/src/utils/config.js b/new-lamassu-admin/src/utils/config.js index c6abad75..4b98c793 100644 --- a/new-lamassu-admin/src/utils/config.js +++ b/new-lamassu-admin/src/utils/config.js @@ -1,6 +1,7 @@ import * as R from 'ramda' const namespaces = { + CASH_OUT: 'cash-out', WALLETS: 'wallets', OPERATOR_INFO: 'operatorInfo', NOTIFICATIONS: 'notifications',