From 01e330ae9891290372c947beaee6a20b19ccecb9 Mon Sep 17 00:00:00 2001 From: Mauricio Navarro Miranda Date: Fri, 27 Mar 2020 03:29:18 -0600 Subject: [PATCH 1/3] feat: felix's wizard base components --- new-lamassu-admin/src/components/Stage.js | 112 +++++++++++++++++++ new-lamassu-admin/src/pages/common.styles.js | 17 +++ 2 files changed, 129 insertions(+) create mode 100644 new-lamassu-admin/src/components/Stage.js create mode 100644 new-lamassu-admin/src/pages/common.styles.js diff --git a/new-lamassu-admin/src/components/Stage.js b/new-lamassu-admin/src/components/Stage.js new file mode 100644 index 00000000..916daf4e --- /dev/null +++ b/new-lamassu-admin/src/components/Stage.js @@ -0,0 +1,112 @@ +import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' +import * as R from 'ramda' +import React, { memo } from 'react' + +import { ReactComponent as CompleteStageIconSpring } from 'src/styling/icons/stage/spring/complete.svg' +import { ReactComponent as CurrentStageIconSpring } from 'src/styling/icons/stage/spring/current.svg' +import { ReactComponent as EmptyStageIconSpring } from 'src/styling/icons/stage/spring/empty.svg' +import { ReactComponent as CompleteStageIconZodiac } from 'src/styling/icons/stage/zodiac/complete.svg' +import { ReactComponent as CurrentStageIconZodiac } from 'src/styling/icons/stage/zodiac/current.svg' +import { ReactComponent as EmptyStageIconZodiac } from 'src/styling/icons/stage/zodiac/empty.svg' +import { + primaryColor, + secondaryColor, + offColor, + disabledColor +} from 'src/styling/variables' + +const styles = { + stages: { + display: 'flex', + alignItems: 'center' + }, + wrapper: { + display: 'flex', + alignItems: 'center', + margin: 0 + }, + stage: { + display: 'flex', + height: 28, + width: 28, + zIndex: 2, + '& > svg': { + height: '100%', + width: '100%', + overflow: 'visible' + } + }, + separator: { + width: 28, + height: 2, + border: [[2, 'solid']], + zIndex: 1 + }, + separatorSpring: { + borderColor: secondaryColor + }, + separatorZodiac: { + borderColor: primaryColor + }, + separatorSpringEmpty: { + borderColor: disabledColor + }, + separatorZodiacEmpty: { + borderColor: offColor + } +} + +const useStyles = makeStyles(styles) + +const Stage = memo(({ stages, currentStage, color = 'spring', className }) => { + if (currentStage < 1 || currentStage > stages) + throw Error('Value of currentStage is invalid') + if (stages < 1) throw Error('Value of stages is invalid') + + const classes = useStyles() + + const separatorClasses = { + [classes.separator]: true, + [classes.separatorSpring]: color === 'spring', + [classes.separatorZodiac]: color === 'zodiac' + } + + const separatorEmptyClasses = { + [classes.separator]: true, + [classes.separatorSpringEmpty]: color === 'spring', + [classes.separatorZodiacEmpty]: color === 'zodiac' + } + + return ( +
+ {R.range(1, currentStage).map(idx => ( +
+ {idx > 1 &&
} +
+ {color === 'spring' && } + {color === 'zodiac' && } +
+
+ ))} +
+ {currentStage > 1 &&
} +
+ {color === 'spring' && } + {color === 'zodiac' && } +
+
+ {R.range(currentStage + 1, stages + 1).map(idx => ( +
+
+
+ {color === 'spring' && } + {color === 'zodiac' && } +
+
+ ))} +
+ ) +}) + +export default Stage diff --git a/new-lamassu-admin/src/pages/common.styles.js b/new-lamassu-admin/src/pages/common.styles.js new file mode 100644 index 00000000..db9958b5 --- /dev/null +++ b/new-lamassu-admin/src/pages/common.styles.js @@ -0,0 +1,17 @@ +export default { + titleWrapper: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + flexDirection: 'row' + }, + titleAndButtonsContainer: { + display: 'flex' + }, + iconButton: { + border: 'none', + outline: 0, + backgroundColor: 'transparent', + cursor: 'pointer' + } +} From af95a366c64add15c376cbaae20e3225c13d1f8f Mon Sep 17 00:00:00 2001 From: Mauricio Navarro Miranda Date: Sat, 22 Feb 2020 00:41:41 -0600 Subject: [PATCH 2/3] feat: add cash-out screen feat: add cash-out route feat: add cash-out table component feat: add cash-out page feat: add wizard splash for enable cashout feat: wizard component for enable cash-out feat: use wizard to enable cash-out fix: denominations are numbers feat: update cashout denominations config on gql feat: refetch cashout infos after config save fix: use default table for cashout table fix: move cashout table closer to parent --- .../src/pages/Cashout/Cashout.js | 246 ++++++++++++++++++ .../src/pages/Cashout/CashoutTable.js | 132 ++++++++++ new-lamassu-admin/src/pages/Cashout/Wizard.js | 192 ++++++++++++++ .../src/pages/Cashout/WizardSplash.js | 64 +++++ new-lamassu-admin/src/routing/routes.js | 7 + new-lamassu-admin/src/utils/config.js | 1 + 6 files changed, 642 insertions(+) create mode 100644 new-lamassu-admin/src/pages/Cashout/Cashout.js create mode 100644 new-lamassu-admin/src/pages/Cashout/CashoutTable.js create mode 100644 new-lamassu-admin/src/pages/Cashout/Wizard.js create mode 100644 new-lamassu-admin/src/pages/Cashout/WizardSplash.js 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', From 1cf4168294b5cfabed965165fbb71a188e447152 Mon Sep 17 00:00:00 2001 From: Mauricio Navarro Miranda Date: Tue, 5 May 2020 23:10:00 -0500 Subject: [PATCH 3/3] feat: use Namespaced table and wizard on Cashout --- .../src/pages/Cashout/Cashout.js | 271 ++++-------------- .../src/pages/Cashout/CashoutTable.js | 132 --------- new-lamassu-admin/src/pages/Cashout/Wizard.js | 225 ++++----------- .../src/pages/Cashout/WizardSplash.js | 57 ++-- .../src/pages/Cashout/WizardStep.js | 130 +++++++++ .../src/pages/Cashout/WizardStep.styles.js | 26 ++ new-lamassu-admin/src/pages/Cashout/helper.js | 41 +++ new-lamassu-admin/src/routing/routes.js | 12 +- new-lamassu-admin/src/utils/config.js | 2 +- 9 files changed, 337 insertions(+), 559 deletions(-) delete mode 100644 new-lamassu-admin/src/pages/Cashout/CashoutTable.js create mode 100644 new-lamassu-admin/src/pages/Cashout/WizardStep.js create mode 100644 new-lamassu-admin/src/pages/Cashout/WizardStep.styles.js create mode 100644 new-lamassu-admin/src/pages/Cashout/helper.js 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 - ) => ( - - ) - )} - - - - ) -} - -/* 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 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} - -
- ) -} - -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.

-
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} + +
+ + ) +} + +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',