diff --git a/new-lamassu-admin/public/fullexample.commissions.png b/new-lamassu-admin/public/fullexample.commissions.png new file mode 100644 index 00000000..92fbf325 Binary files /dev/null and b/new-lamassu-admin/public/fullexample.commissions.png differ diff --git a/new-lamassu-admin/public/fullexample.locale.png b/new-lamassu-admin/public/fullexample.locale.png new file mode 100644 index 00000000..6e76177e Binary files /dev/null and b/new-lamassu-admin/public/fullexample.locale.png differ diff --git a/new-lamassu-admin/public/fullexample.twilio.png b/new-lamassu-admin/public/fullexample.twilio.png new file mode 100644 index 00000000..a8d44125 Binary files /dev/null and b/new-lamassu-admin/public/fullexample.twilio.png differ diff --git a/new-lamassu-admin/public/wizard-background.svg b/new-lamassu-admin/public/wizard-background.svg new file mode 100644 index 00000000..a783cf7d --- /dev/null +++ b/new-lamassu-admin/public/wizard-background.svg @@ -0,0 +1,204 @@ + + + welcome-page-background + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/new-lamassu-admin/src/components/Modal.js b/new-lamassu-admin/src/components/Modal.js index e38214b3..f190004f 100644 --- a/new-lamassu-admin/src/components/Modal.js +++ b/new-lamassu-admin/src/components/Modal.js @@ -43,16 +43,20 @@ const styles = { flex: 1, padding: [[0, 24]] }, - content: ({ small }) => ({ + content: ({ small, xl }) => ({ width: '100%', display: 'flex', flexDirection: 'column', flex: 1, - padding: small ? [[0, 16]] : [[0, 32]] + padding: xl ? [[0, 60 + 28]] : small ? [[0, 16]] : [[0, 32]] }), - button: ({ small }) => ({ - padding: 0, - margin: small ? [[12, 12, 'auto', 'auto']] : [[16, 16, 'auto', 'auto']] + button: ({ small, xl }) => ({ + padding: [[0, 0, xl ? 26 : 0, 0]], + margin: xl + ? [[0, 0, 'auto', 'auto']] + : small + ? [[12, 12, 'auto', 'auto']] + : [[16, 16, 'auto', 'auto']] }), header: { display: 'flex' @@ -70,6 +74,7 @@ const Modal = ({ infoPanelHeight, title, small, + xl, infoPanel, handleClose, children, @@ -79,14 +84,9 @@ const Modal = ({ closeOnBackdropClick, ...props }) => { - const classes = useStyles({ - width, - height, - small, - infoPanelHeight - }) + const classes = useStyles({ width, height, small, infoPanelHeight, xl }) const TitleCase = small ? H4 : H1 - const closeSize = small ? 16 : 20 + const closeSize = xl ? 28 : small ? 16 : 20 const innerClose = (evt, reason) => { if (!closeOnBackdropClick && reason === 'backdropClick') return diff --git a/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js index d6257735..8df0225b 100644 --- a/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js +++ b/new-lamassu-admin/src/components/booleanPropertiesTable/BooleanPropertiesTable.js @@ -25,13 +25,13 @@ const BooleanCell = ({ name }) => { } const BooleanPropertiesTable = memo( - ({ title, disabled, data, elements, save }) => { + ({ title, disabled, data, elements, save, forcedEditing = false }) => { const initialValues = _.fromPairs(elements.map(it => [it.name, ''])) const schemaValidation = _.fromPairs( elements.map(it => [it.name, Yup.boolean().required()]) ) - const [editing, setEditing] = useState(false) + const [editing, setEditing] = useState(forcedEditing) const classes = useStyles() diff --git a/new-lamassu-admin/src/components/buttons/Button.styles.js b/new-lamassu-admin/src/components/buttons/Button.styles.js index ce8c1cf7..064e3409 100644 --- a/new-lamassu-admin/src/components/buttons/Button.styles.js +++ b/new-lamassu-admin/src/components/buttons/Button.styles.js @@ -8,10 +8,12 @@ import { spacer } from 'src/styling/variables' -const { h3 } = typographyStyles +const { h1, h3 } = typographyStyles const pickSize = size => { switch (size) { + case 'xl': + return spacer * 7.625 case 'sm': return spacer * 4 case 'lg': @@ -28,10 +30,11 @@ export default { }, button: ({ size }) => { const height = pickSize(size) - const shadowSize = height / 12 + const shadowSize = size === 'xl' ? 3 : height / 12 + const padding = size === 'xl' ? 20 : height / 2 return { - extend: h3, + extend: size === 'xl' ? h1 : h3, border: 'none', color: white, cursor: 'pointer', @@ -51,7 +54,7 @@ export default { }, shadowSize, height, - padding: `0 ${height / 2}px`, + padding: `0 ${padding}px`, borderRadius: height / 4, boxShadow: `0 ${shadowSize}px ${secondaryColorDark}`, '&:hover': { diff --git a/new-lamassu-admin/src/pages/Notifications/Notifications.js b/new-lamassu-admin/src/pages/Notifications/Notifications.js index 439a179d..29e54893 100644 --- a/new-lamassu-admin/src/pages/Notifications/Notifications.js +++ b/new-lamassu-admin/src/pages/Notifications/Notifications.js @@ -38,7 +38,17 @@ const SAVE_CONFIG = gql` const FIELDS_WIDTH = 130 -const Notifications = ({ name: SCREEN_KEY }) => { +// TODO: what about 'onlySetup' 'onlyFiat'? +const Notifications = ({ + name: SCREEN_KEY, + displaySetup = true, + displayTransactionAlerts = true, + displayFiatAlerts = true, + displayCryptoAlerts = true, + displayOverrides = true, + displayTitle = true, + wizard = false +}) => { const [section, setSection] = useState(null) const [error, setError] = useState(null) const [editingKey, setEditingKey] = useState(null) @@ -92,27 +102,38 @@ const Notifications = ({ name: SCREEN_KEY }) => { return ( - - -
- -
- -
- -
- -
- - -
- -
- - -
+ {displayTitle && } + {displaySetup && ( +
+ +
+ )} + {displayTransactionAlerts && ( +
+ +
+ )} + {displayFiatAlerts && ( +
+ + {displayOverrides && } +
+ )} + {displayCryptoAlerts && ( +
+ + {displayOverrides && ( + + )} +
+ )}
) } diff --git a/new-lamassu-admin/src/pages/Notifications/sections/Setup.js b/new-lamassu-admin/src/pages/Notifications/sections/Setup.js index d5816c09..3c5991bb 100644 --- a/new-lamassu-admin/src/pages/Notifications/sections/Setup.js +++ b/new-lamassu-admin/src/pages/Notifications/sections/Setup.js @@ -66,16 +66,20 @@ const Row = ({ namespace, forceDisable }) => { const useStyles = makeStyles({ mainTable: { width + }, + wizardTable: { + width: 930 } }) -const Setup = ({ forceDisable }) => { +const Setup = ({ wizard, forceDisable }) => { + const widthAdjust = wizard ? 20 : 0 const classes = useStyles() return ( - +
- + {Object.keys(sizes).map(it => ( - ))} diff --git a/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js index 2b45b452..d7ab6c63 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/ContactInfo.js @@ -103,10 +103,10 @@ const styles = R.merge(globalStyles, contactInfoStyles) const contactUseStyles = makeStyles(styles) -const ContactInfo = () => { +const ContactInfo = ({ wizard }) => { const classes = contactUseStyles() - const [editing, setEditing] = useState(false) + const [editing, setEditing] = useState(wizard || false) const [error, setError] = useState(null) const [saveConfig] = useMutation(SAVE_CONFIG, { @@ -298,14 +298,16 @@ const ContactInfo = () => { -
- - - Sharing your information with your customers through your machines - allows them to contact you in case there's a problem with a machine in - your network or a transaction. - -
+ {!wizard && ( +
+ + + Sharing your information with your customers through your machines + allows them to contact you in case there's a problem with a machine + in your network or a transaction. + +
+ )} ) } diff --git a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js index 87b7d1d6..794530d2 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/OperatorInfo.js @@ -54,7 +54,7 @@ const innerRoutes = [ } ] -const Routes = () => ( +const Routes = ({ wizard }) => ( ( {innerRoutes.map(({ route, component: Page, key }) => ( - + ))} ) -const OperatorInfo = () => { +const OperatorInfo = ({ wizard = false }) => { const classes = useStyles() const history = useHistory() const location = useLocation() @@ -90,7 +90,7 @@ const OperatorInfo = () => { onClick={onClick} />
- +
diff --git a/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting/ReceiptPrinting.js b/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting/ReceiptPrinting.js index b48a2787..4bd94446 100644 --- a/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting/ReceiptPrinting.js +++ b/new-lamassu-admin/src/pages/OperatorInfo/ReceiptPrinting/ReceiptPrinting.js @@ -25,7 +25,7 @@ const SAVE_CONFIG = gql` } ` -const ReceiptPrinting = memo(() => { +const ReceiptPrinting = memo(({ wizard }) => { const classes = useStyles() const { data } = useQuery(GET_CONFIG) @@ -70,6 +70,7 @@ const ReceiptPrinting = memo(() => { {receiptPrintingConfig.active ? 'Yes' : 'No'} { const classes = useStyles() @@ -46,7 +47,7 @@ const FormRenderer = ({
{elements.map(({ component, code, display, inputProps }) => ( - + ({ + code: 'bitgo', + name: 'BitGo', + title: 'BitGo (Wallet)', + elements: [ + { + code: 'token', + display: 'API Token', + component: TextInput, + face: true, + long: true + }, + { + code: 'environment', + display: 'Environment', + component: Autocomplete, + inputProps: { + options: ['prod', 'test'] + }, + face: true + }, + { + code: `${code.toLowerCase()}WalletId`, + display: `${code.toUpperCase()} Wallet ID`, + component: TextInput + }, + { + code: `${code.toLowerCase()}WalletPassphrase`, + display: `${code.toUpperCase()} Wallet Passphrase`, + component: SecretInput + } + ], + validationSchema: bitgo.validationSchema +}) diff --git a/new-lamassu-admin/src/pages/Wallet/helper.js b/new-lamassu-admin/src/pages/Wallet/helper.js index 1cd75fcf..167e234e 100644 --- a/new-lamassu-admin/src/pages/Wallet/helper.js +++ b/new-lamassu-admin/src/pages/Wallet/helper.js @@ -13,7 +13,8 @@ const WalletSchema = Yup.object().shape({ zeroConf: Yup.string().required('Required') }) -const getElements = (cryptoCurrencies, accounts) => { +const getElements = (cryptoCurrencies, accounts, wizard = false) => { + const widthAdjust = wizard ? 11 : 0 const viewCryptoCurrency = it => R.compose( R.prop(['display']), @@ -36,7 +37,7 @@ const getElements = (cryptoCurrencies, accounts) => { { name: 'id', header: 'Cryptocurrency', - width: 180, + width: 180 - widthAdjust, view: viewCryptoCurrency, size: 'sm', editable: false @@ -46,7 +47,7 @@ const getElements = (cryptoCurrencies, accounts) => { size: 'sm', stripe: true, view: getDisplayName('ticker'), - width: 190, + width: 190 - widthAdjust, input: Autocomplete, inputProps: { options: getOptions('ticker'), @@ -60,7 +61,7 @@ const getElements = (cryptoCurrencies, accounts) => { size: 'sm', stripe: true, view: getDisplayName('wallet'), - width: 190, + width: 190 - widthAdjust, input: Autocomplete, inputProps: { options: getOptions('wallet'), @@ -74,7 +75,7 @@ const getElements = (cryptoCurrencies, accounts) => { size: 'sm', stripe: true, view: getDisplayName('exchange'), - width: 190, + width: 190 - widthAdjust, input: Autocomplete, inputProps: { options: getOptions('exchange'), @@ -89,7 +90,7 @@ const getElements = (cryptoCurrencies, accounts) => { stripe: true, view: getDisplayName('zeroConf'), input: Autocomplete, - width: 190, + width: 190 - widthAdjust, inputProps: { options: getOptions('zeroConf'), valueProp: 'code', diff --git a/new-lamassu-admin/src/pages/Wizard/Radio.styles.js b/new-lamassu-admin/src/pages/Wizard/Radio.styles.js new file mode 100644 index 00000000..85975f70 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/Radio.styles.js @@ -0,0 +1,33 @@ +import { spacer, primaryColor } from 'src/styling/variables' + +const LABEL_WIDTH = 150 + +export default { + radioGroup: { + flexDirection: 'row', + width: 600 + }, + radioLabel: { + width: LABEL_WIDTH, + height: 48 + }, + mdForm: { + width: 385 + }, + infoMessage: { + display: 'flex', + marginBottom: 20, + '& > p': { + width: 330, + marginTop: 4, + marginLeft: 16 + } + }, + actionButton: { + marginBottom: spacer * 4 + }, + actionButtonLink: { + textDecoration: 'none', + color: primaryColor + } +} diff --git a/new-lamassu-admin/src/pages/Wizard/Wizard.js b/new-lamassu-admin/src/pages/Wizard/Wizard.js new file mode 100644 index 00000000..68545b57 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/Wizard.js @@ -0,0 +1,263 @@ +import { useQuery } from '@apollo/react-hooks' +import { makeStyles, Dialog, DialogContent } from '@material-ui/core' +import classnames from 'classnames' +import gql from 'graphql-tag' +import * as R from 'ramda' +import React, { useReducer, useEffect } from 'react' +import { Switch, Route, useRouteMatch } from 'react-router-dom' + +import Twilio from 'src/pages/Wizard/components/Twilio' +import { backgroundColor } from 'src/styling/variables' +import { fromNamespace, namespaces } from 'src/utils/config' + +import { schema as CommissionsSchema } from '../Commissions/helper' +import { LocaleSchema } from '../Locales/helper' +import twilio from '../Services/schemas/twilio' +import { WalletSchema } from '../Wallet/helper' + +import Commissions from './components/Commissions' +import Footer from './components/Footer' +import Locales from './components/Locales' +import N from './components/Notifications' +import WizardOperatorInfo from './components/OperatorInfo' +import Wallet from './components/Wallet' +import Welcome from './components/Welcome' + +const useStyles = makeStyles({ + wrapper: { + display: 'flex', + flexGrow: 1, + padding: '1rem 0', + flexDirection: 'column', + justifyContent: 'space-between' + }, + welcomeBackground: { + background: 'url(/wizard-background.svg) no-repeat center center fixed', + backgroundSize: 'cover', + backgroundColor: backgroundColor + } +}) + +const GET_GLOBAL_CONFIG = gql` + query getData { + config + accounts + } +` +const validateSteps = ({ config, accounts }) => { + const steps = [ + { + namespace: 'welcome', + isComplete: true + }, + { + namespace: namespaces.WALLETS, + tag: 'Wallet settings', + p: ( + <> + Your wallet settings are the first step for this wizard. We'll start + by setting one of cryptocurrency to get you up and running, but you + can later setup as many cryptocurrencies as you want. + + ), + isComplete: (() => { + const wallets = fromNamespace(namespaces.WALLETS)(config) + const wizardCoin = Object.keys(wallets).find( + k => k.endsWith('_wizard') && wallets[k] + ) + const coinCode = wizardCoin && wizardCoin.replace('_wizard', '') + const wallet = coinCode && fromNamespace(coinCode)(wallets) + return wallet && WalletSchema.isValidSync(wallet) + })() + }, + { + namespace: namespaces.LOCALE, + tag: 'Locales', + p: ( + <> + From the Locales page, you can define some important default settings + of your machines. These values will be the default values of all + machines you'll later add to your network. Default settings keep you + from having to enther the same values everytime you add a new machine. + Once a machine is added, you may override some of these values in the + overrides section. + + ), + isComplete: LocaleSchema.isValidSync( + fromNamespace(namespaces.LOCALE)(config) + ) + }, + { + namespace: 'twilio', + tag: 'Twilio (SMS service)', + p: ( + <> + Twilio is used for SMS operator notifications, phone number collection + for compliance, and 1-confirmation redemptions on cash-out + transactions. +
+ You'll need to configure Twilio if you're offering cash-out or any + compliance options + + ), + isComplete: twilio.validationSchema.isValidSync(accounts?.twilio) || R.isEmpty(accounts?.twilio) + }, + { + namespace: namespaces.COMMISSIONS, + tag: 'Commissions', + p: ( + <> + From the Commissions page, you can define all the commissions of your + machines. The values set here will be default values of all machines + you'll later add to your network. Default settings keep you from + having to enter the same values everytime you add a new machine. Once + a machine is added, you may override these values per machine and per + cryptocurrency in the overrides section. + + ), + isComplete: CommissionsSchema.isValidSync( + fromNamespace(namespaces.COMMISSIONS)(config) + ) + }, + { + namespace: namespaces.NOTIFICATIONS, + tag: 'Notifications', + p: ( + <> + Your notification settings will allow customize what notifications you + get and where. You can later override all default balance alerts setup + here. + + ), + isComplete: true + }, + { + namespace: namespaces.OPERATOR_INFO, + tag: 'Operator info', + p: <>, + isComplete: true + } + ] + + return steps +} + +const findStepByName = namespace => + R.findIndex(R.propEq('namespace', namespace)) + +const getNextIndex = namespace => R.compose(R.add(1), findStepByName(namespace)) + +const initialState = { + steps: [], + current: 'welcome', + next: namespaces.WALLETS +} + +const reducer = (state, action) => { + switch (action.type) { + case 'wizard/INIT': + return { ...state, steps: validateSteps(action.payload) } + case 'wizard/VALIDATE_STEP': + return { ...state, steps: validateSteps(action.payload) } + case 'wizard/SET_STEP': + // eslint-disable-next-line no-case-declarations + const nextIndex = getNextIndex(action.payload)(state.steps) + // eslint-disable-next-line no-case-declarations + const current = findStepByName(action.payload)(state.steps) + + return { + ...state, + ...state.steps[current], + current: action.payload, + next: + state.steps[current].isComplete && state.steps[nextIndex] + ? state.steps[nextIndex].namespace + : null + } + + default: + return state + } +} + +function Wizard() { + const { path } = useRouteMatch() + + const { data } = useQuery(GET_GLOBAL_CONFIG) + const [state, dispatch] = useReducer(reducer, initialState) + const { steps } = state + + useEffect(() => { + data && + dispatch({ + type: 'wizard/INIT', + payload: { config: data.config, accounts: data.accounts } + }) + }, [data]) + + const classes = useStyles() + + if (!steps || !steps.length) return
+ + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {state.current !== 'welcome' &&
} +
+ ) +} + +export default Wizard diff --git a/new-lamassu-admin/src/pages/Wizard/components/AllSet.js b/new-lamassu-admin/src/pages/Wizard/components/AllSet.js new file mode 100644 index 00000000..8732b94f --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/AllSet.js @@ -0,0 +1,71 @@ +import { useQuery } from '@apollo/react-hooks' +import gql from 'graphql-tag' +import * as R from 'ramda' +import React, { useEffect, useState } from 'react' + +import { NamespacedTable as EditableTable } from 'src/components/editableTable' +import { P, H4 } from 'src/components/typography' +import { getElements } from 'src/pages/Wallet/helper' +import { fromNamespace } from 'src/utils/config' + +const GET_INFO = gql` + query getData { + config + accounts + accountsConfig { + code + display + class + cryptos + } + cryptoCurrencies { + code + display + } + } +` + +const Wallet = ({ dispatch, namespace }) => { + const { data } = useQuery(GET_INFO) + const [dispatched, setDispatched] = useState(false) + + const config = data?.config && fromNamespace('wallets')(data.config) + + const wizardCoin = + config && Object.keys(config).find(k => k.endsWith('_wizard') && config[k]) + const coinCode = wizardCoin && wizardCoin.replace('_wizard', '') + + const accountsConfig = data?.accountsConfig + const cryptoCurrencies = + data?.cryptoCurrencies?.filter(({ code }) => code === coinCode) ?? [] + + useEffect(() => { + if (dispatched || !data?.config) return + + dispatch({ + type: 'wizard/VALIDATE_STEP', + payload: { config: data.config, accounts: data.accounts } + }) + setDispatched(true) + }, [data, dispatch, dispatched]) + + return ( + <> +

All set

+

+ This are your wallet settings. You can later edit these and add + additional coins. +

+ + + ) +} + +export default Wallet diff --git a/new-lamassu-admin/src/pages/Wizard/components/Blockcypher.js b/new-lamassu-admin/src/pages/Wizard/components/Blockcypher.js new file mode 100644 index 00000000..d49e29f6 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Blockcypher.js @@ -0,0 +1,105 @@ +import { useMutation, useQuery } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import gql from 'graphql-tag' +import React, { useState, useEffect } from 'react' + +import { Link } from 'src/components/buttons' +import { RadioGroup } from 'src/components/inputs' +import { P, H4 } from 'src/components/typography' +import FormRenderer from 'src/pages/Services/FormRenderer' +import schema from 'src/pages/Services/schemas' +import styles from 'src/pages/Wizard/Radio.styles' + +const useStyles = makeStyles({ + ...styles, + radioGroup: styles.radioGroup, + radioLabel: { + ...styles.radioLabel, + width: 200 + } +}) + +const GET_CONFIG = gql` + { + accounts + } +` +const SAVE_ACCOUNTS = gql` + mutation Save($accounts: JSONObject) { + saveAccounts(accounts: $accounts) + } +` + +const options = [ + { + code: 'enable', + display: 'I will enable cash-out' + }, + { + code: 'disable', + display: "I won't enable cash-out" + } +] + +const Blockcypher = () => { + const { data } = useQuery(GET_CONFIG) + const accounts = data?.accounts ?? [] + + const classes = useStyles() + const [saveConfig] = useMutation(SAVE_ACCOUNTS) + const [currentCode, setCurrentCode] = useState('disable') + + useEffect(() => { + if (!accounts?.blockcypher) return + schema.blockcypher.validationSchema.isValidSync(accounts.blockcypher) && + setCurrentCode('enable') + }, [accounts]) + + const handleRadio = cashoutOrNot => { + setCurrentCode(cashoutOrNot) + cashoutOrNot === 'disable' && save({}) + } + + const save = blockcypher => { + const accounts = { blockcypher } + return saveConfig({ variables: { accounts } }) + } + + return ( + <> +

Blockcypher

+

+ If you are enabling cash-out services,{' '} + + + create a Blockcypher account. + + +

+ handleRadio(event.target.value)} + /> +
+ {currentCode === 'enable' && ( + + )} +
+ + ) +} + +export default Blockcypher diff --git a/new-lamassu-admin/src/pages/Wizard/components/ChooseCoin.js b/new-lamassu-admin/src/pages/Wizard/components/ChooseCoin.js new file mode 100644 index 00000000..42f2c519 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/ChooseCoin.js @@ -0,0 +1,76 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import gql from 'graphql-tag' +import React, { useState, useEffect } from 'react' + +import { RadioGroup } from 'src/components/inputs' +import { H4 } from 'src/components/typography' +import styles from 'src/pages/Wizard/Radio.styles' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' + +const useStyles = makeStyles(styles) + +const GET_CONFIG = gql` + { + config + cryptoCurrencies { + code + display + } + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` + +const WalletCoin = () => { + const classes = useStyles() + + const { data } = useQuery(GET_CONFIG) + const [saveConfig] = useMutation(SAVE_CONFIG) + const [currentCode, setCurrentCode] = useState(null) + const cryptoCurrencies = data?.cryptoCurrencies ?? [] + + const wallets = data?.config && fromNamespace('wallets')(data.config) + const wizardCoin = + wallets && + Object.keys(wallets).find(k => k.endsWith('_wizard') && wallets[k]) + + useEffect(() => { + setCurrentCode(wizardCoin && wizardCoin.replace('_wizard', '')) + }, [wizardCoin]) + + const save = code => { + setCurrentCode(code) + + const keys = {} + for (const { code: c } of cryptoCurrencies) { + keys[`${c}_wizard`] = c === code + } + + keys[`${code}_zeroConf`] = 'no-zero-conf' + keys[`${code}_ticker`] = 'kraken' + + const config = toNamespace(namespaces.WALLETS)(keys) + return saveConfig({ variables: { config } }) + } + + return ( + <> +

Choose your first cryptocurrency

+ + save(event.target.value)} + /> + + ) +} + +export default WalletCoin diff --git a/new-lamassu-admin/src/pages/Wizard/components/ChooseExchange.js b/new-lamassu-admin/src/pages/Wizard/components/ChooseExchange.js new file mode 100644 index 00000000..ea4e9b5a --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/ChooseExchange.js @@ -0,0 +1,162 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import gql from 'graphql-tag' +import * as R from 'ramda' +import React, { useState, useEffect, useCallback } from 'react' + +import { ActionButton } from 'src/components/buttons' +import { RadioGroup } from 'src/components/inputs' +import { H4, Info3 } from 'src/components/typography' +import FormRenderer from 'src/pages/Services/FormRenderer' +import schema from 'src/pages/Services/schemas' +import styles from 'src/pages/Wizard/Radio.styles' +import { ReactComponent as InverseLinkIcon } from 'src/styling/icons/action/external link/white.svg' +import { ReactComponent as LinkIcon } from 'src/styling/icons/action/external link/zodiac.svg' +import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' + +import { getItems } from './getItems' + +const useStyles = makeStyles(styles) + +const GET_CONFIG = gql` + { + config + accounts + accountsConfig { + code + display + class + cryptos + } + cryptoCurrencies { + code + display + } + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` +const SAVE_ACCOUNTS = gql` + mutation Save($accounts: JSONObject) { + saveAccounts(accounts: $accounts) + } +` +const ChooseExchange = () => { + const classes = useStyles() + const { data } = useQuery(GET_CONFIG) + const [saveConfig] = useMutation(SAVE_CONFIG) + const [saveAccounts] = useMutation(SAVE_ACCOUNTS) + const [currentCode, setCurrentCode] = useState(null) + const [currentName, setCurrentName] = useState(null) + const accounts = data?.accounts ?? [] + const accountsConfig = data?.accountsConfig ?? [] + + const walletConfigs = + data?.config && fromNamespace(namespaces.WALLETS)(data.config) + const wizardCoin = + walletConfigs && + Object.keys(walletConfigs).find( + k => k.endsWith('_wizard') && walletConfigs[k] + ) + + const coinCode = wizardCoin && wizardCoin.replace('_wizard', '') + const exchanges = getItems(accountsConfig, accounts, 'exchange', coinCode) + + const setCurrName = useCallback( + currentCode => { + const ex = + currentCode && + R.union(exchanges.filled, exchanges.unfilled).find( + ({ code }) => code === currentCode + ) + setCurrentName(ex && ex.display) + }, + [exchanges] + ) + + useEffect(() => { + if (currentCode) return + const exchange = + walletConfigs && coinCode && fromNamespace(coinCode)(walletConfigs) + setCurrentCode(exchange && exchange.exchange) + setCurrName(exchange && exchange.exchange) + }, [walletConfigs, coinCode, currentCode, setCurrName]) + + const save = exchange => { + setCurrentCode(exchange) + setCurrName(exchange) + const config = toNamespace(`wallets_${coinCode}`)({ + active: true, + exchange + }) + return saveConfig({ variables: { config } }) + } + + const saveExchange = name => exchange => { + const accounts = { [name]: exchange } + return saveAccounts({ variables: { accounts } }) + } + + const supportArticles = { + kraken: + 'https://support.lamassu.is/hc/en-us/articles/115001206891-Kraken-trading', + itbit: + 'https://support.lamassu.is/hc/en-us/articles/360026195032-itBit-trading', + bitstamp: + 'https://support.lamassu.is/hc/en-us/articles/115001206911-Bitstamp-trading' + } + + return ( +
+

Choose your exchange

+ save(event.target.value)} + /> + {['kraken', 'itbit', 'bitstamp'].includes(currentCode) && ( + <> +
+ + + Make sure you set up {currentName} to enter the necessary + information below. Please follow the instructions on our support + page if you haven’t. + +
+ + + {currentName} trading + + + +

Enter exchange information

+ + + )} +
+ ) +} + +export default ChooseExchange diff --git a/new-lamassu-admin/src/pages/Wizard/components/ChooseWallet.js b/new-lamassu-admin/src/pages/Wizard/components/ChooseWallet.js new file mode 100644 index 00000000..899356fa --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/ChooseWallet.js @@ -0,0 +1,160 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import gql from 'graphql-tag' +import * as R from 'ramda' +import React, { useState, useEffect } from 'react' + +import { ActionButton } from 'src/components/buttons' +import { RadioGroup } from 'src/components/inputs' +import { H4, Info3 } from 'src/components/typography' +import FormRenderer from 'src/pages/Services/FormRenderer' +import schema from 'src/pages/Services/schemas' +import bitgo from 'src/pages/Services/schemas/singlebitgo' +import styles from 'src/pages/Wizard/Radio.styles' +import { ReactComponent as InverseLinkIcon } from 'src/styling/icons/action/external link/white.svg' +import { ReactComponent as LinkIcon } from 'src/styling/icons/action/external link/zodiac.svg' +import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' + +import { getItems } from './getItems' + +const useStyles = makeStyles(styles) + +const GET_CONFIG = gql` + { + config + accounts + accountsConfig { + code + display + class + cryptos + } + cryptoCurrencies { + code + display + } + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` +const SAVE_ACCOUNTS = gql` + mutation Save($accounts: JSONObject) { + saveAccounts(accounts: $accounts) + } +` +const WalletCoin = () => { + const classes = useStyles() + const { data } = useQuery(GET_CONFIG) + const [saveConfig] = useMutation(SAVE_CONFIG) + const [saveAccounts] = useMutation(SAVE_ACCOUNTS) + const [currentCode, setCurrentCode] = useState(null) + const accounts = data?.accounts ?? [] + const accountsConfig = data?.accountsConfig ?? [] + + const walletConfigs = + data?.config && fromNamespace(namespaces.WALLETS)(data.config) + const wizardCoin = + walletConfigs && + Object.keys(walletConfigs).find( + k => k.endsWith('_wizard') && walletConfigs[k] + ) + + const coinCode = wizardCoin && wizardCoin.replace('_wizard', '') + const wallets = getItems(accountsConfig, accounts, 'wallet', coinCode) + + useEffect(() => { + if (currentCode) return + const wallet = + walletConfigs && coinCode && fromNamespace(coinCode)(walletConfigs) + setCurrentCode(wallet && wallet.wallet) + }, [walletConfigs, coinCode, currentCode]) + + const save = wallet => { + setCurrentCode(wallet) + const config = toNamespace(`wallets_${coinCode}`)({ + active: true, + wallet + }) + return saveConfig({ variables: { config } }) + } + + const saveWallet = name => wallet => { + const accounts = { [name]: wallet } + return saveAccounts({ variables: { accounts } }) + } + + return ( +
+

Choose your wallet

+ save(event.target.value)} + /> + {currentCode === 'bitgo' && ( + <> +
+ + + Make sure you set up a BitGo wallet to enter the necessary + information below. Please follow the instructions on our support + page if you haven’t. + +
+ + + Support article + + +

Enter wallet information

+ + + )} + {currentCode === 'bitcoind' && ( +
+ + + To setup bitcoind please read our instructions from our support + article. We’ll later verify if your wallet is correctly set up for + you to progress + +
+ )} + {currentCode === 'infura' && ( + <> +

Enter wallet information

+ + + )} +
+ ) +} + +export default WalletCoin diff --git a/new-lamassu-admin/src/pages/Wizard/components/Commissions.js b/new-lamassu-admin/src/pages/Wizard/components/Commissions.js new file mode 100644 index 00000000..422f85d3 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Commissions.js @@ -0,0 +1,80 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import gql from 'graphql-tag' +import * as R from 'ramda' +import React, { useEffect } from 'react' + +import { Table as EditableTable } from 'src/components/editableTable' +import Section from 'src/components/layout/Section' +import TitleSection from 'src/components/layout/TitleSection' +import styles from 'src/pages/AddMachine/styles' +import { mainFields, defaults, schema } from 'src/pages/Commissions/helper' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' + +const useStyles = makeStyles(styles) + +const GET_DATA = gql` + query getData { + config, + accounts + } +` +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` + +function Commissions({ dispatch, namespace }) { + const classes = useStyles() + const { data, refetch } = useQuery(GET_DATA) + + const [saveConfig] = useMutation(SAVE_CONFIG, { + refetchQueries: () => ['getData'] + }) + + useEffect(() => { + dispatch({ type: 'wizard/SET_STEP', payload: namespace }) + }, [dispatch, namespace]) + + const config = data?.config && fromNamespace(namespace)(data.config) + const values = config && !R.isEmpty(config) ? config : defaults + + const save = it => { + const config = toNamespace(namespace)(it[namespace][0]) + return saveConfig({ variables: { config } }) + .then(() => refetch()) + .then(({ data }) => { + return dispatch({ + type: 'wizard/VALIDATE_STEP', + payload: { accounts: data.accounts, config: data.config } + }) + }) + } + + const currency = R.path(['fiatCurrency'])( + fromNamespace(namespaces.LOCALE)(data?.config) + ) + + return ( +
+ +
+ +
+
+ ) +} + +export default Commissions diff --git a/new-lamassu-admin/src/pages/Wizard/components/Footer.js b/new-lamassu-admin/src/pages/Wizard/components/Footer.js new file mode 100644 index 00000000..7405bb76 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Footer.js @@ -0,0 +1,151 @@ +import Modal from 'src/components/Modal' + +import { makeStyles, Drawer, Grid } from '@material-ui/core' +import ClickAwayListener from '@material-ui/core/ClickAwayListener' +import classnames from 'classnames' +import * as R from 'ramda' +import React, { useState } from 'react' +import { useHistory } from 'react-router-dom' + +import Stepper from 'src/components/Stepper' +import { Button, Link } from 'src/components/buttons' +import { P, H2, Info2 } from 'src/components/typography' +import { spacer } from 'src/styling/variables' +import { URI } from 'src/utils/apollo' + +const getStepperProps = (current, steps) => ({ + steps: R.length(steps), + currentStep: R.compose( + R.add(1), + R.findIndex(R.propEq('namespace', current)) + )(steps) +}) + +const useStyles = makeStyles(() => ({ + drawer: { + borderTop: 'none', + boxShadow: '0 0 4px 0 rgba(0, 0, 0, 0.08)' + }, + wrapper: { + padding: '32px 0', + flexGrow: 1, + height: 264 + }, + smallWrapper: { + height: 84 + }, + subtitle: { + marginTop: spacer, + marginBottom: 6, + lineHeight: 1.25, + display: 'inline' + }, + modal: { + background: 'none', + boxShadow: 'none', + }, +})) + +function Footer({ next, current, steps: collection, path, tag, p }) { + const classes = useStyles() + const history = useHistory() + const [open, setOpen] = useState(true) + const [fullExample, setFullExample] = useState(false) + + const handleClick = () => history.push(`${path}/${next}`) + const handleClickAway = () => setOpen(false) + const { currentStep, steps } = getStepperProps(current, collection) + + const wrapperClassNames = { + [classes.wrapper]: true, + [classes.smallWrapper]: !open + } + + return ( + + setOpen(true)} + anchor={'bottom'} + open={true} + variant={'persistent'} + classes={{ paperAnchorDockedBottom: classes.drawer }}> +
+ + +

Setup Lamassu Admin

+ {open && {tag}} + {open &&

{p}

} +
+ + + {steps && currentStep && ( + + )} + + +
+ {open && ( + + + { setFullExample(true) }}>See full example + + + + + + + + )} +
+ { setFullExample(false) }} + open={fullExample}> + {`${current} + +
+
+ ) +} + +export default Footer diff --git a/new-lamassu-admin/src/pages/Wizard/components/Locales.js b/new-lamassu-admin/src/pages/Wizard/components/Locales.js new file mode 100644 index 00000000..892f5c61 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Locales.js @@ -0,0 +1,101 @@ +import { useQuery, useMutation } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import gql from 'graphql-tag' +import * as R from 'ramda' +import React, { useEffect } from 'react' + +import { Table as EditableTable } from 'src/components/editableTable' +import Section from 'src/components/layout/Section' +import TitleSection from 'src/components/layout/TitleSection' +import styles from 'src/pages/AddMachine/styles' +import { + mainFields, + localeDefaults as defaults, + LocaleSchema as schema +} from 'src/pages/Locales/helper' +import { fromNamespace, toNamespace } from 'src/utils/config' + +const useStyles = makeStyles(styles) + +const GET_DATA = gql` + query getData { + config + accounts + currencies { + code + display + } + countries { + code + display + } + cryptoCurrencies { + code + display + } + languages { + code + display + } + machines { + name + deviceId + } + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` + +function Locales({ dispatch, namespace }) { + const classes = useStyles() + const { data, refetch } = useQuery(GET_DATA) + + const [saveConfig] = useMutation(SAVE_CONFIG, { + refetchQueries: () => ['getData'] + }) + + useEffect(() => { + dispatch({ type: 'wizard/SET_STEP', payload: namespace }) + }, [dispatch, namespace]) + + const config = data?.config && fromNamespace(namespace)(data.config) + const locale = config && !R.isEmpty(config) ? config : defaults + + const save = it => { + const config = toNamespace(namespace)(it[namespace][0]) + return saveConfig({ variables: { config } }) + .then(() => refetch()) + .then(({ data }) => { + return dispatch({ + type: 'wizard/VALIDATE_STEP', + payload: { accounts: data.accounts, config: data.config } + }) + }) + } + + return ( +
+ +
+ +
+
+ ) +} + +export default Locales diff --git a/new-lamassu-admin/src/pages/Wizard/components/Mailgun.js b/new-lamassu-admin/src/pages/Wizard/components/Mailgun.js new file mode 100644 index 00000000..4e69eca6 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Mailgun.js @@ -0,0 +1,138 @@ +import { useMutation, useQuery } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import gql from 'graphql-tag' +import React, { useState, useEffect } from 'react' + +import { ActionButton } from 'src/components/buttons' +import { RadioGroup } from 'src/components/inputs' +import { H4, Info3 } from 'src/components/typography' +import FormRenderer from 'src/pages/Services/FormRenderer' +import schema from 'src/pages/Services/schemas' +import styles from 'src/pages/Wizard/Radio.styles' +import { ReactComponent as InverseLinkIcon } from 'src/styling/icons/action/external link/white.svg' +import { ReactComponent as LinkIcon } from 'src/styling/icons/action/external link/zodiac.svg' +import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg' +import { fromNamespace, toNamespace, namespaces } from 'src/utils/config' + +const useStyles = makeStyles({ + ...styles, + radioGroup: { + ...styles.radioGroup, + width: 768 + }, + radioLabel: { + ...styles.radioLabel, + width: 300 + } +}) + +const GET_CONFIG = gql` + { + config + accounts + } +` + +const SAVE_CONFIG = gql` + mutation Save($config: JSONObject) { + saveConfig(config: $config) + } +` +const SAVE_ACCOUNTS = gql` + mutation Save($accounts: JSONObject) { + saveAccounts(accounts: $accounts) + } +` + +const options = [ + { + code: 'enabled', + display: 'Yes, send notifications to my email' + }, + { + code: 'disabled', + display: "No, don't send email notifications" + } +] + +const Mailgun = () => { + const classes = useStyles() + const { data } = useQuery(GET_CONFIG) + const [saveConfig] = useMutation(SAVE_CONFIG) + const [saveAccounts] = useMutation(SAVE_ACCOUNTS) + const [emailActive, setEmailActive] = useState(false) + const accounts = data?.accounts ?? [] + + const emailConfig = + data?.config && + fromNamespace(namespaces.NOTIFICATIONS + '_email')(data.config) + + useEffect(() => { + if (emailActive) return + emailConfig && setEmailActive(emailConfig?.active ? 'enabled' : 'disabled') + }, [emailActive, emailConfig]) + + const handleRadio = enabled => { + setEmailActive(enabled) + save(enabled === 'enabled') + } + + const save = active => { + const config = toNamespace(`notifications_email`)({ active }) + return saveConfig({ variables: { config } }) + } + + const saveAccount = mailgun => { + const accounts = { mailgun } + return saveAccounts({ variables: { accounts } }) + } + + return ( +
+

Do you want to get notifications via email?

+ handleRadio(event.target.value)} + /> + +
+ + + To get email notifications, you’ll need to setup Mailgun. Check out + our article on how to set it up. + +
+ + + Email notifications with Mailgun + + + + {emailActive === 'enabled' && ( + <> +

Mailgun credentials

+ + + )} +
+ ) +} + +export default Mailgun diff --git a/new-lamassu-admin/src/pages/Wizard/components/Notifications.js b/new-lamassu-admin/src/pages/Wizard/components/Notifications.js new file mode 100644 index 00000000..d2975c58 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Notifications.js @@ -0,0 +1,109 @@ +import { makeStyles } from '@material-ui/core' +import Grid from '@material-ui/core/Grid' +import React, { useState, useEffect } from 'react' + +import Sidebar from 'src/components/layout/Sidebar' +import TitleSection from 'src/components/layout/TitleSection' +import addMachineStyles from 'src/pages/AddMachine/styles' +import Notifications from 'src/pages/Notifications/Notifications' +import { namespaces } from 'src/utils/config' + +import Mailgun from './Mailgun' + +const styles = { + ...addMachineStyles, + grid: { + flex: 1, + height: '100%' + }, + content: { + marginLeft: 48, + paddingTop: 15 + } +} + +const useStyles = makeStyles(styles) + +const EMAIL = 'Email' +const SETUP_CHANNELS = 'Setup channels' +const TRANSACTION_ALERTS = 'Transaction alerts' +const FIAT_BALANCE_ALERTS = 'Fiat balance alerts' +const CRYPTO_BALANCE_ALERTS = 'Crypto balance alerts' + +const pages = [ + EMAIL, + SETUP_CHANNELS, + TRANSACTION_ALERTS, + FIAT_BALANCE_ALERTS, + CRYPTO_BALANCE_ALERTS +] + +const N = ({ dispatch, namespace }) => { + useEffect(() => { + dispatch({ type: 'wizard/SET_STEP', payload: namespace }) + }, [dispatch, namespace]) + const [selected, setSelected] = useState(EMAIL) + const classes = useStyles() + + const isSelected = it => selected === it + + return ( +
+ + + it} + onClick={it => setSelected(it)} + /> +
+ {isSelected(EMAIL) && } + {isSelected(SETUP_CHANNELS) && ( + + )} + {isSelected(TRANSACTION_ALERTS) && ( + + )} + {isSelected(FIAT_BALANCE_ALERTS) && ( + + )} + {isSelected(CRYPTO_BALANCE_ALERTS) && ( + + )} +
+
+
+ ) +} + +export default N diff --git a/new-lamassu-admin/src/pages/Wizard/components/OperatorInfo.js b/new-lamassu-admin/src/pages/Wizard/components/OperatorInfo.js new file mode 100644 index 00000000..d0c2413f --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/OperatorInfo.js @@ -0,0 +1,22 @@ +import { makeStyles } from '@material-ui/core' +import React, { useEffect } from 'react' + +import styles from 'src/pages/AddMachine/styles' +import OperatorInfo from 'src/pages/OperatorInfo/OperatorInfo' + +const useStyles = makeStyles(styles) + +function WizardOperatorInfo({ dispatch, namespace }) { + const classes = useStyles() + useEffect(() => { + dispatch({ type: 'wizard/SET_STEP', payload: namespace }) + }, [dispatch, namespace]) + + return ( +
+ +
+ ) +} + +export default WizardOperatorInfo diff --git a/new-lamassu-admin/src/pages/Wizard/components/Twilio.js b/new-lamassu-admin/src/pages/Wizard/components/Twilio.js new file mode 100644 index 00000000..3f5d6f12 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Twilio.js @@ -0,0 +1,143 @@ +import { useMutation, useQuery } from '@apollo/react-hooks' +import { makeStyles } from '@material-ui/core' +import classnames from 'classnames' +import gql from 'graphql-tag' +import React, { useEffect, useState } from 'react' + +import { H1, Label1, H4, P } from 'src/components/typography' +import addMachineStyles from 'src/pages/AddMachine/styles' +import { + styles as globalStyles, + contactInfoStyles +} from 'src/pages/OperatorInfo/OperatorInfo.styles' +import FormRenderer from 'src/pages/Services/FormRenderer' +import twilio from 'src/pages/Services/schemas/twilio' +import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg' +import { RadioGroup } from 'src/components/inputs' +import styles from 'src/pages/Wizard/Radio.styles' +import Tooltip from 'src/components/Tooltip' +import { IconButton } from 'src/components/buttons' +import { ReactComponent as HelpIcon } from 'src/styling/icons/action/help/zodiac.svg' + +const GET_CONFIG = gql` + { + config + accounts + } +` + +const SAVE_ACCOUNTS = gql` + mutation Save($accounts: JSONObject) { + saveAccounts(accounts: $accounts) + } +` + +const useStyles = makeStyles({ + ...styles, + ...globalStyles, + ...contactInfoStyles, + ...addMachineStyles, + content: { + width: 820, + flex: 0 + }, + radioLabel: { + ...styles.radioLabel, + width: 280 + } +}) + +const options = [ + { + code: 'enable', + display: 'Yes, I will add two-way machines' + }, + { + code: 'disable', + display: "No, not for now" + } +] + +function Twilio({ dispatch, namespace }) { + const { data, refetch } = useQuery(GET_CONFIG) + const [saveAccounts] = useMutation(SAVE_ACCOUNTS) + const accounts = data?.accounts ?? [] + const config = data?.config ?? [] + + const [enable, setEnable] = useState('disable') + + useEffect(() => { + if (!accounts?.twilio) return + twilio.validationSchema.isValidSync(accounts.twilio) && setEnable('enable') + }, [accounts]) + + const handleRadio = enableOrNot => { + setEnable(enableOrNot) + enableOrNot === 'disable' && save({}) + enableOrNot === 'enable' && save({ enable: true, ...accounts?.twilio }) + } + + useEffect(() => { + dispatch({ type: 'wizard/SET_STEP', payload: namespace }) + }, [dispatch, namespace]) + const classes = useStyles() + + const save = twilio => { + const accounts = { twilio } + return saveAccounts({ variables: { accounts } }) + .then(() => refetch()) + .then(({ data }) => { + return dispatch({ + type: 'wizard/VALIDATE_STEP', + payload: { accounts: data.accounts, config: data.config } + }) + }) + } + + return ( +
+
+

Twilio (SMS service)

+

+ Will you setup a two way machine? + +

+ Two-way machines allow your customers not only to buy (cash-in) but also sell cryptocurrencies (cash-out).

+

+ To get your admin up and running, you’ll only need an SMS service for cash-out transactions. If you’re using one-way machines, select “No” to skip this step for now. You can later set it up within the Lamassu Admin.

+
+ +

+ + handleRadio(event.target.value)} + /> + + +
+ + + Before configuring Twilio, create an account and phone number to use + the Admin. + +
+ {enable === 'enable' && ( + + )} +
+
+ ) +} + +export default Twilio diff --git a/new-lamassu-admin/src/pages/Wizard/components/Wallet.js b/new-lamassu-admin/src/pages/Wizard/components/Wallet.js new file mode 100644 index 00000000..370a8749 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Wallet.js @@ -0,0 +1,57 @@ +import { makeStyles } from '@material-ui/core' +import React, { useState, useEffect } from 'react' + +import Sidebar from 'src/components/layout/Sidebar' +import TitleSection from 'src/components/layout/TitleSection' +import styles from 'src/pages/AddMachine/styles' +import ChooseCoin from 'src/pages/Wizard/components/ChooseCoin' +import ChooseWallet from 'src/pages/Wizard/components/ChooseWallet' + +import AllSet from './AllSet' +import Blockcypher from './Blockcypher' +import ChooseExchange from './ChooseExchange' + +const useStyles = makeStyles(styles) + +const COIN = 'Choose cryptocurrency' +const WALLET = 'Choose wallet' +const EXCHANGE = 'Exchange' +const CASHOUT_OR_NOT = 'Blockcypher' +const ALL_SET = 'All set' + +const pages = [COIN, WALLET, EXCHANGE, CASHOUT_OR_NOT, ALL_SET] + +const Wallet = ({ dispatch, namespace }) => { + useEffect(() => { + dispatch({ type: 'wizard/SET_STEP', payload: namespace }) + }, [dispatch, namespace]) + const [selected, setSelected] = useState(COIN) + const classes = useStyles() + + const isSelected = it => selected === it + + return ( +
+
+ +
+
+ it} + onClick={it => setSelected(it)} + /> +
+ {isSelected(COIN) && } + {isSelected(WALLET) && } + {isSelected(EXCHANGE) && } + {isSelected(CASHOUT_OR_NOT) && } + {isSelected(ALL_SET) && } +
+
+
+ ) +} + +export default Wallet diff --git a/new-lamassu-admin/src/pages/Wizard/components/Welcome.js b/new-lamassu-admin/src/pages/Wizard/components/Welcome.js new file mode 100644 index 00000000..446fbceb --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/Welcome.js @@ -0,0 +1,53 @@ +import { makeStyles } from '@material-ui/core' +import React, { useEffect } from 'react' +import { useHistory } from 'react-router-dom' + +import { Button } from 'src/components/buttons' +import { H1, P } from 'src/components/typography' +import { comet } from 'src/styling/variables' + +const styles = { + welcome: { + textAlign: 'center', + paddingTop: 256 + }, + title: { + lineHeight: 1, + fontSize: 48 + }, + getStarted: { + fontSize: 24, + fontWeight: 500, + marginBottom: 54, + color: comet + } +} + +const useStyles = makeStyles(styles) + +function Welcome({ dispatch }) { + const classes = useStyles() + + useEffect(() => { + dispatch({ type: 'wizard/SET_STEP', payload: 'welcome' }) + }, [dispatch]) + + const history = useHistory() + const handleClick = () => history.push('/wizard/wallets') + + return ( +
+

Welcome to Lamassu Admin

+

+ To get started, we’ve put together wizard that will +
+ help set up you need before start adding machines. +

+ +
+ ) +} + +export default Welcome diff --git a/new-lamassu-admin/src/pages/Wizard/components/getItems.js b/new-lamassu-admin/src/pages/Wizard/components/getItems.js new file mode 100644 index 00000000..e84b04ce --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/components/getItems.js @@ -0,0 +1,21 @@ +import * as R from 'ramda' + +import schema from 'src/pages/Services/schemas' +const contains = crypto => R.compose(R.contains(crypto), R.prop('cryptos')) +const sameClass = type => R.propEq('class', type) +const filterConfig = (crypto, type) => + R.filter(it => sameClass(type)(it) && contains(crypto)(it)) +export const getItems = (accountsConfig, accounts, type, crypto) => { + const fConfig = filterConfig(crypto, type)(accountsConfig) + const find = code => accounts && accounts[code] + + const [filled, unfilled] = R.partition(({ code }) => { + const account = find(code) + if (!schema[code]) return true + + const { validationSchema } = schema[code] + return validationSchema.isValidSync(account) + })(fConfig) + + return { filled, unfilled } +} diff --git a/new-lamassu-admin/src/pages/Wizard/index.js b/new-lamassu-admin/src/pages/Wizard/index.js new file mode 100644 index 00000000..6935ebe1 --- /dev/null +++ b/new-lamassu-admin/src/pages/Wizard/index.js @@ -0,0 +1,3 @@ +import Wizard from './Wizard' + +export default Wizard diff --git a/new-lamassu-admin/src/routing/routes.js b/new-lamassu-admin/src/routing/routes.js index 85b10900..f3509514 100644 --- a/new-lamassu-admin/src/routing/routes.js +++ b/new-lamassu-admin/src/routing/routes.js @@ -18,6 +18,7 @@ import Services from 'src/pages/Services/Services' import Transactions from 'src/pages/Transactions/Transactions' import Triggers from 'src/pages/Triggers' import WalletSettings from 'src/pages/Wallet/Wallet' +import Wizard from 'src/pages/Wizard' import { namespaces } from 'src/utils/config' const tree = [ @@ -157,6 +158,7 @@ const Routes = () => ( + {flattened.map(({ route, component: Page, key }) => (
ChannelChannel + {startCase(it)}