chore: use monorepo organization
This commit is contained in:
parent
deaf7d6ecc
commit
a687827f7e
1099 changed files with 8184 additions and 11535 deletions
43
packages/admin-ui/src/pages/Wizard/Radio.module.css
Normal file
43
packages/admin-ui/src/pages/Wizard/Radio.module.css
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
.radioGroup {
|
||||
flex-direction: row;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.radioLabel {
|
||||
width: 150px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.mailgunRadioGroup {
|
||||
flex-direction: row;
|
||||
width: 768px;
|
||||
}
|
||||
|
||||
.mailgunRadioLabel {
|
||||
width: 300px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.mdForm {
|
||||
width: 385px;
|
||||
}
|
||||
|
||||
.infoMessage {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.infoMessage > p {
|
||||
width: 330px;
|
||||
margin-top: 4px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.actionButtonLink {
|
||||
text-decoration: none;
|
||||
color: var(--zodiac);
|
||||
}
|
||||
94
packages/admin-ui/src/pages/Wizard/Wizard.jsx
Normal file
94
packages/admin-ui/src/pages/Wizard/Wizard.jsx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { useQuery, gql } from '@apollo/client'
|
||||
import Dialog from '@mui/material/Dialog'
|
||||
import DialogContent from '@mui/material/DialogContent'
|
||||
import classnames from 'classnames'
|
||||
import React, { useState, useContext } from 'react'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { getWizardStep, STEPS } from 'src/pages/Wizard/helper'
|
||||
|
||||
import AppContext from 'src/AppContext'
|
||||
|
||||
import Footer from './components/Footer'
|
||||
|
||||
const GET_DATA = gql`
|
||||
query getData {
|
||||
config
|
||||
accounts
|
||||
cryptoCurrencies {
|
||||
code
|
||||
display
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Wizard = ({ fromAuthRegister }) => {
|
||||
const { data, loading } = useQuery(GET_DATA)
|
||||
const history = useHistory()
|
||||
const { setWizardTested } = useContext(AppContext)
|
||||
|
||||
const [step, setStep] = useState(0)
|
||||
const [open, setOpen] = useState(true)
|
||||
|
||||
const [footerExp, setFooterExp] = useState(false)
|
||||
|
||||
if (loading) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const wizardStep = getWizardStep(data?.config, data?.cryptoCurrencies)
|
||||
|
||||
const shouldGoBack =
|
||||
history.length && !history.location.state?.fromAuthRegister
|
||||
|
||||
if (wizardStep === 0) {
|
||||
setWizardTested(true)
|
||||
shouldGoBack ? history.goBack() : history.push('/')
|
||||
}
|
||||
|
||||
const isWelcome = step === 0
|
||||
|
||||
const start = () => {
|
||||
setFooterExp(false)
|
||||
}
|
||||
|
||||
const doContinue = () => {
|
||||
if (step >= STEPS.length - 1) {
|
||||
setOpen(false)
|
||||
history.push('/')
|
||||
}
|
||||
|
||||
const nextStep = step === 0 && wizardStep ? wizardStep : step + 1
|
||||
|
||||
setFooterExp(true)
|
||||
setStep(nextStep)
|
||||
}
|
||||
|
||||
const current = STEPS[step]
|
||||
const classNames = {
|
||||
'flex flex-col justify-between py-4 px-0 bg-white': true,
|
||||
'bg-[url(/wizard-background.svg)] bg-no-repeat bg-center bg-fixed bg-cover':
|
||||
isWelcome,
|
||||
'blur-sm pointer-events-none': footerExp
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog fullScreen open={open}>
|
||||
<DialogContent className={classnames(classNames)}>
|
||||
<current.Component doContinue={doContinue} isActive={!footerExp} />
|
||||
</DialogContent>
|
||||
{!isWelcome && (
|
||||
<Footer
|
||||
currentStep={step}
|
||||
steps={STEPS.length - 1}
|
||||
exImage={current.exImage}
|
||||
subtitle={current.subtitle}
|
||||
text={current.text}
|
||||
open={footerExp}
|
||||
start={start}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default Wizard
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
import { useQuery, useMutation, gql } from '@apollo/client'
|
||||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
import Section from 'src/components/layout/Section'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
import { mainFields, defaults, getSchema } from 'src/pages/Commissions/helper'
|
||||
|
||||
import { Table as EditableTable } from 'src/components/editableTable'
|
||||
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
const GET_DATA = gql`
|
||||
query getData {
|
||||
config
|
||||
}
|
||||
`
|
||||
const SAVE_CONFIG = gql`
|
||||
mutation Save($config: JSONObject) {
|
||||
saveConfig(config: $config)
|
||||
}
|
||||
`
|
||||
|
||||
function Commissions({ isActive, doContinue }) {
|
||||
const { data } = useQuery(GET_DATA)
|
||||
|
||||
const [saveConfig] = useMutation(SAVE_CONFIG, {
|
||||
onCompleted: doContinue
|
||||
})
|
||||
|
||||
const save = it => {
|
||||
const config = toNamespace('commissions')(it.commissions[0])
|
||||
return saveConfig({ variables: { config } })
|
||||
}
|
||||
|
||||
const currency = R.path(['fiatCurrency'])(
|
||||
fromNamespace(namespaces.LOCALE)(data?.config)
|
||||
)
|
||||
|
||||
const locale = fromNamespace(namespaces.LOCALE)(data?.config)
|
||||
|
||||
return (
|
||||
<div className="w-[1132px] h-full mx-auto flex-1 flex flex-col">
|
||||
<TitleSection title="Commissions" />
|
||||
<Section>
|
||||
<EditableTable
|
||||
title="Default setup"
|
||||
rowSize="lg"
|
||||
titleLg
|
||||
name="commissions"
|
||||
initialValues={defaults}
|
||||
enableEdit
|
||||
forceAdd={isActive}
|
||||
save={save}
|
||||
validationSchema={getSchema(locale)}
|
||||
data={[]}
|
||||
elements={mainFields(currency)}
|
||||
/>
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Commissions
|
||||
104
packages/admin-ui/src/pages/Wizard/components/Footer.jsx
Normal file
104
packages/admin-ui/src/pages/Wizard/components/Footer.jsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import Drawer from '@mui/material/Drawer'
|
||||
import Grid from '@mui/material/Grid'
|
||||
import React, { useState } from 'react'
|
||||
import Modal from 'src/components/Modal'
|
||||
import Stepper from 'src/components/Stepper'
|
||||
import { P, H2, Info2 } from 'src/components/typography'
|
||||
|
||||
import { Button, Link } from 'src/components/buttons'
|
||||
|
||||
function Footer({ currentStep, steps, subtitle, text, exImage, open, start }) {
|
||||
const [fullExample, setFullExample] = useState(false)
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
anchor={'bottom'}
|
||||
open={true}
|
||||
variant={'persistent'}
|
||||
classes={{ paperAnchorDockedBottom: 'border-t-0 shadow-sm' }}>
|
||||
<div className={`py-8 flex-grow ${open ? 'h-[264px]' : 'h-[84px]'}`}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="baseline">
|
||||
<Grid
|
||||
item
|
||||
xs={5}
|
||||
container
|
||||
direction={open ? 'column' : 'row'}
|
||||
justifyContent="flex-start"
|
||||
alignItems="baseline">
|
||||
<H2 className="m-0 mr-8">Setup Lamassu Admin</H2>
|
||||
<Info2 className="mt-2 mb-2 leading-tight inline">{subtitle}</Info2>
|
||||
{open && <P>{text}</P>}
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={4}
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-end"
|
||||
spacing={5}>
|
||||
<Grid item xs={12}>
|
||||
{steps && currentStep && (
|
||||
<Stepper currentStep={currentStep} steps={steps} />
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{open && (
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="baseline">
|
||||
<Grid
|
||||
item
|
||||
xs={5}
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-start">
|
||||
<Link
|
||||
onClick={() => {
|
||||
setFullExample(true)
|
||||
}}>
|
||||
See full example
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={4}
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="flex-start"
|
||||
alignItems="flex-end"
|
||||
spacing={5}>
|
||||
<Grid item>
|
||||
<Button size="lg" onClick={start}>
|
||||
Get Started
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</div>
|
||||
<Modal
|
||||
closeOnEscape={true}
|
||||
closeOnBackdropClick={true}
|
||||
className="bg-transparent shadow-none"
|
||||
xl={true}
|
||||
width={1152 + 120 + 56}
|
||||
handleClose={() => {
|
||||
setFullExample(false)
|
||||
}}
|
||||
open={fullExample}>
|
||||
<img width={1152} src={exImage} alt="" />
|
||||
</Modal>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
94
packages/admin-ui/src/pages/Wizard/components/Locales.jsx
Normal file
94
packages/admin-ui/src/pages/Wizard/components/Locales.jsx
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { useQuery, useMutation, gql } from '@apollo/client'
|
||||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
import Section from 'src/components/layout/Section'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
|
||||
import { Table as EditableTable } from 'src/components/editableTable'
|
||||
import {
|
||||
mainFields,
|
||||
localeDefaults as defaults,
|
||||
LocaleSchema as schema
|
||||
} from 'src/pages/Locales/helper'
|
||||
import { toNamespace } from 'src/utils/config'
|
||||
|
||||
import { getConfiguredCoins } from '../helper'
|
||||
|
||||
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({ isActive, doContinue }) {
|
||||
const { data } = useQuery(GET_DATA)
|
||||
|
||||
const [saveConfig] = useMutation(SAVE_CONFIG, {
|
||||
onCompleted: doContinue
|
||||
})
|
||||
|
||||
const save = it => {
|
||||
const config = toNamespace('locale')(it.locale[0])
|
||||
return saveConfig({ variables: { config } })
|
||||
}
|
||||
|
||||
const cryptoCurrencies = getConfiguredCoins(
|
||||
data?.config || {},
|
||||
data?.cryptoCurrencies || []
|
||||
)
|
||||
|
||||
const onChangeCoin = (prev, curr, setValue) => setValue(curr)
|
||||
|
||||
return (
|
||||
<div className="w-[1132px] h-full mx-auto flex-1 flex flex-col">
|
||||
<TitleSection title="Locales" />
|
||||
<Section>
|
||||
<EditableTable
|
||||
title="Default settings"
|
||||
rowSize="lg"
|
||||
titleLg
|
||||
name="locale"
|
||||
initialValues={defaults}
|
||||
forceAdd={isActive}
|
||||
enableEdit
|
||||
save={save}
|
||||
validationSchema={schema}
|
||||
data={[]}
|
||||
elements={mainFields(
|
||||
R.mergeRight(data, { cryptoCurrencies }),
|
||||
onChangeCoin
|
||||
)}
|
||||
/>
|
||||
</Section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Locales
|
||||
123
packages/admin-ui/src/pages/Wizard/components/Mailgun.jsx
Normal file
123
packages/admin-ui/src/pages/Wizard/components/Mailgun.jsx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { useMutation, useQuery, gql } from "@apollo/client";
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { H4, Info3 } from 'src/components/typography'
|
||||
import FormRenderer from 'src/pages/Services/FormRenderer'
|
||||
import InverseLinkIcon from 'src/styling/icons/action/external link/white.svg?react'
|
||||
import LinkIcon from 'src/styling/icons/action/external link/zodiac.svg?react'
|
||||
import WarningIcon from 'src/styling/icons/warning-icon/comet.svg?react'
|
||||
|
||||
import { ActionButton } from 'src/components/buttons'
|
||||
import { RadioGroup } from 'src/components/inputs'
|
||||
import mailgunSchema from 'src/pages/Services/schemas/mailgun'
|
||||
import classes from 'src/pages/Wizard/Radio.module.css'
|
||||
import { fromNamespace, toNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
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 { 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 (
|
||||
<div className={classes.mdForm}>
|
||||
<H4>Do you want to get notifications via email?</H4>
|
||||
<RadioGroup
|
||||
labelClassName={classes.mailgunRadioLabel}
|
||||
className={classes.mailgunRadioGroup}
|
||||
options={options}
|
||||
value={emailActive}
|
||||
onChange={event => handleRadio(event.target.value)}
|
||||
/>
|
||||
|
||||
<div className={classes.infoMessage}>
|
||||
<WarningIcon />
|
||||
<Info3>
|
||||
To get email notifications, you’ll need to set up Mailgun. Check out
|
||||
our article on how to set it up.
|
||||
</Info3>
|
||||
</div>
|
||||
<ActionButton
|
||||
className={classes.actionButton}
|
||||
color="primary"
|
||||
Icon={LinkIcon}
|
||||
InverseIcon={InverseLinkIcon}>
|
||||
<a
|
||||
className={classes.actionButtonLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="https://support.lamassu.is/hc/en-us/articles/115001203991-Email-notifications-with-Mailgun">
|
||||
Email notifications with Mailgun
|
||||
</a>
|
||||
</ActionButton>
|
||||
|
||||
{emailActive === 'enabled' && (
|
||||
<>
|
||||
<H4>Mailgun credentials</H4>
|
||||
<FormRenderer
|
||||
value={accounts.mailgun}
|
||||
save={saveAccount}
|
||||
elements={mailgunSchema.elements}
|
||||
validationSchema={mailgunSchema.getValidationSchema(accounts.mailgun)}
|
||||
buttonLabel={'Save'}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Mailgun
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
import Grid from '@mui/material/Grid'
|
||||
import React, { useState } from 'react'
|
||||
import Sidebar from 'src/components/layout/Sidebar'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
import Notifications from 'src/pages/Notifications/Notifications'
|
||||
|
||||
import { namespaces } from 'src/utils/config'
|
||||
|
||||
import Mailgun from './Mailgun'
|
||||
|
||||
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 = () => {
|
||||
const [selected, setSelected] = useState(EMAIL)
|
||||
|
||||
const isSelected = it => selected === it
|
||||
|
||||
return (
|
||||
<div className="w-[1132px] h-full mx-auto flex-1 flex flex-col">
|
||||
<TitleSection title="Notifications"></TitleSection>
|
||||
<Grid container className="flex-1 h-full">
|
||||
<Sidebar
|
||||
data={pages}
|
||||
isSelected={isSelected}
|
||||
displayName={it => it}
|
||||
onClick={it => setSelected(it)}
|
||||
/>
|
||||
<div className="ml-12 pt-4">
|
||||
{isSelected(EMAIL) && <Mailgun />}
|
||||
{isSelected(SETUP_CHANNELS) && (
|
||||
<Notifications
|
||||
name={namespaces.NOTIFICATIONS}
|
||||
wizard={true}
|
||||
displayCryptoAlerts={false}
|
||||
displayOverrides={false}
|
||||
displayTitle={false}
|
||||
displayTransactionAlerts={false}
|
||||
displayFiatAlerts={false}
|
||||
/>
|
||||
)}
|
||||
{isSelected(TRANSACTION_ALERTS) && (
|
||||
<Notifications
|
||||
name={namespaces.NOTIFICATIONS}
|
||||
displayCryptoAlerts={false}
|
||||
displayOverrides={false}
|
||||
displayTitle={false}
|
||||
displaySetup={false}
|
||||
displayFiatAlerts={false}
|
||||
/>
|
||||
)}
|
||||
{isSelected(FIAT_BALANCE_ALERTS) && (
|
||||
<Notifications
|
||||
name={namespaces.NOTIFICATIONS}
|
||||
displayCryptoAlerts={false}
|
||||
displayOverrides={false}
|
||||
displayTitle={false}
|
||||
displayTransactionAlerts={false}
|
||||
displaySetup={false}
|
||||
/>
|
||||
)}
|
||||
{isSelected(CRYPTO_BALANCE_ALERTS) && (
|
||||
<Notifications
|
||||
name={namespaces.NOTIFICATIONS}
|
||||
displaySetup={false}
|
||||
displayOverrides={false}
|
||||
displayTitle={false}
|
||||
displayTransactionAlerts={false}
|
||||
displayFiatAlerts={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default N
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react'
|
||||
// import OperatorInfo from 'src/pages/OperatorInfo'
|
||||
|
||||
function WizardOperatorInfo() {
|
||||
return (
|
||||
<div className="w-[1132px] h-full mx-auto flex-1 flex flex-col">
|
||||
{/* <OperatorInfo wizard={true}></OperatorInfo> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WizardOperatorInfo
|
||||
138
packages/admin-ui/src/pages/Wizard/components/Twilio.jsx
Normal file
138
packages/admin-ui/src/pages/Wizard/components/Twilio.jsx
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { useMutation, useQuery, gql } from '@apollo/client'
|
||||
import classnames from 'classnames'
|
||||
import React, { useState } from 'react'
|
||||
import { HelpTooltip } from 'src/components/Tooltip'
|
||||
import { H1, H4, Label1, P } from 'src/components/typography'
|
||||
import FormRenderer from 'src/pages/Services/FormRenderer'
|
||||
import WarningIcon from 'src/styling/icons/warning-icon/comet.svg?react'
|
||||
|
||||
import { Button, SupportLinkButton } from 'src/components/buttons'
|
||||
import { RadioGroup } from 'src/components/inputs'
|
||||
import twilio from 'src/pages/Services/schemas/twilio'
|
||||
|
||||
import sharedClasses from './Wallet/Shared.module.css'
|
||||
import classes from './Twilio.module.css'
|
||||
|
||||
const GET_CONFIG = gql`
|
||||
{
|
||||
config
|
||||
accounts
|
||||
}
|
||||
`
|
||||
|
||||
const SAVE_ACCOUNTS = gql`
|
||||
mutation Save($accounts: JSONObject) {
|
||||
saveAccounts(accounts: $accounts)
|
||||
}
|
||||
`
|
||||
|
||||
const options = [
|
||||
{
|
||||
code: 'enable',
|
||||
display: 'Yes, I will'
|
||||
},
|
||||
{
|
||||
code: 'disable',
|
||||
display: 'No, not for now'
|
||||
}
|
||||
]
|
||||
|
||||
function Twilio({ doContinue }) {
|
||||
const [selected, setSelected] = useState(null)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const { data, refetch } = useQuery(GET_CONFIG)
|
||||
const [saveAccounts] = useMutation(SAVE_ACCOUNTS, {
|
||||
onCompleted: doContinue
|
||||
})
|
||||
|
||||
const accounts = data?.accounts ?? []
|
||||
|
||||
const onSelect = e => {
|
||||
setSelected(e.target.value)
|
||||
setError(false)
|
||||
}
|
||||
|
||||
const clickContinue = () => {
|
||||
if (!selected) return setError(true)
|
||||
doContinue()
|
||||
}
|
||||
|
||||
const save = twilio => {
|
||||
const accounts = { twilio }
|
||||
return saveAccounts({ variables: { accounts } }).then(() => refetch())
|
||||
}
|
||||
|
||||
const titleClasses = {
|
||||
'ml-2 mb-2': true,
|
||||
[sharedClasses.error]: error
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classes.content}>
|
||||
<H1>Twilio (SMS service)</H1>
|
||||
<div className="flex items-end">
|
||||
<H4 noMargin className={classnames(titleClasses)}>
|
||||
Will you setup a two way machine or compliance?
|
||||
</H4>
|
||||
<HelpTooltip width={304}>
|
||||
<P>
|
||||
Two-way machines allow your customers not only to buy (cash-in)
|
||||
but also sell cryptocurrencies (cash-out).
|
||||
</P>
|
||||
<P>
|
||||
You’ll need an SMS service for cash-out transactions and for any
|
||||
compliance triggers
|
||||
</P>
|
||||
</HelpTooltip>
|
||||
</div>
|
||||
|
||||
<RadioGroup
|
||||
labelClassName={classes.radioLabel}
|
||||
className={sharedClasses.radioGroup}
|
||||
options={options}
|
||||
value={selected}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
|
||||
<div className="flex gap-4 mt-5 mb-8 items-center">
|
||||
<WarningIcon />
|
||||
<Label1 noMargin>
|
||||
To set up Twilio please read the instructions from our support
|
||||
portal.
|
||||
</Label1>
|
||||
</div>
|
||||
<SupportLinkButton
|
||||
link="https://support.lamassu.is/hc/en-us/articles/115001203951-Twilio-for-SMS"
|
||||
label="Twilio for SMS"
|
||||
/>
|
||||
|
||||
{selected === 'enable' && (
|
||||
<>
|
||||
<H4 noMargin>Enter credentials</H4>
|
||||
<FormRenderer
|
||||
xs={6}
|
||||
save={save}
|
||||
value={accounts.twilio}
|
||||
elements={twilio.elements}
|
||||
validationSchema={twilio.getValidationSchema(accounts.twilio)}
|
||||
buttonLabel={'Continue'}
|
||||
buttonClass={sharedClasses.formButton}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{selected !== 'enable' && (
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={clickContinue}
|
||||
className={sharedClasses.button}>
|
||||
Continue
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Twilio
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
.content {
|
||||
width: 820px;
|
||||
}
|
||||
|
||||
.radioLabel {
|
||||
width: 280px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 1200px;
|
||||
height: 100px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 8px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import { useQuery, useMutation, gql } from "@apollo/client";
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
import { P, H4 } from 'src/components/typography'
|
||||
import { getElements, WalletSchema } from 'src/pages/Wallet/helper'
|
||||
|
||||
import { Button } from 'src/components/buttons'
|
||||
import { NamespacedTable as EditableTable } from 'src/components/editableTable'
|
||||
import { toNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
import classes from './Shared.module.css'
|
||||
const GET_INFO = gql`
|
||||
query getData {
|
||||
config
|
||||
accounts
|
||||
accountsConfig {
|
||||
code
|
||||
display
|
||||
class
|
||||
cryptos
|
||||
}
|
||||
cryptoCurrencies {
|
||||
code
|
||||
display
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const SAVE_CONFIG = gql`
|
||||
mutation Save($config: JSONObject, $accounts: JSONObject) {
|
||||
saveConfig(config: $config)
|
||||
saveAccounts(accounts: $accounts)
|
||||
}
|
||||
`
|
||||
|
||||
const AllSet = ({ data: currentData, doContinue }) => {
|
||||
const { data } = useQuery(GET_INFO)
|
||||
const [saveConfig] = useMutation(SAVE_CONFIG, {
|
||||
onCompleted: doContinue
|
||||
})
|
||||
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const coin = currentData?.coin
|
||||
|
||||
const accountsConfig = data?.accountsConfig
|
||||
const cryptoCurrencies = data?.cryptoCurrencies ?? []
|
||||
|
||||
const save = () => {
|
||||
const adjustedData = {
|
||||
zeroConfLimit: 0,
|
||||
...currentData
|
||||
}
|
||||
if (!WalletSchema.isValidSync(adjustedData)) return setError(true)
|
||||
|
||||
const withCoin = toNamespace(coin, R.omit('coin', adjustedData))
|
||||
const config = toNamespace(namespaces.WALLETS)(withCoin)
|
||||
setError(false)
|
||||
return saveConfig({ variables: { config } })
|
||||
}
|
||||
|
||||
const presentableData = R.pipe(
|
||||
R.omit(['coin', 'zeroConf', 'zeroConfLimit']),
|
||||
toNamespace(coin)
|
||||
)(currentData)
|
||||
|
||||
const presentableElements = R.filter(
|
||||
R.pipe(
|
||||
R.prop('name'),
|
||||
R.flip(R.includes)(['zeroConf', 'zeroConfLimit']),
|
||||
R.not()
|
||||
),
|
||||
getElements(cryptoCurrencies, accountsConfig, null, true)
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<H4 className={error && classes.error}>All set</H4>
|
||||
<P>
|
||||
These are your wallet settings. You can later edit these and add
|
||||
additional coins.
|
||||
</P>
|
||||
<EditableTable
|
||||
rowSize="lg"
|
||||
titleLg
|
||||
name="All set"
|
||||
namespaces={[coin]}
|
||||
data={presentableData}
|
||||
elements={presentableElements}
|
||||
/>
|
||||
<Button size="lg" onClick={save} className={classes.button}>
|
||||
Continue
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default AllSet
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
import { useMutation, useQuery, gql } from "@apollo/client";
|
||||
import React, { useState } from 'react'
|
||||
import { P, H4 } from 'src/components/typography'
|
||||
import FormRenderer from 'src/pages/Services/FormRenderer'
|
||||
|
||||
import { SupportLinkButton, Button } from 'src/components/buttons'
|
||||
import { RadioGroup } from 'src/components/inputs'
|
||||
import blockcypherSchema from 'src/pages/Services/schemas/blockcypher'
|
||||
|
||||
import classes from './Shared.module.css'
|
||||
|
||||
const GET_CONFIG = gql`
|
||||
{
|
||||
accounts
|
||||
}
|
||||
`
|
||||
const SAVE_ACCOUNTS = gql`
|
||||
mutation SaveAccountsBC($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 = ({ addData }) => {
|
||||
const { data } = useQuery(GET_CONFIG)
|
||||
const [saveConfig] = useMutation(SAVE_ACCOUNTS, {
|
||||
onCompleted: () => addData({ zeroConf: 'blockcypher' })
|
||||
})
|
||||
|
||||
const [selected, setSelected] = useState(null)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const accounts = data?.accounts ?? []
|
||||
|
||||
const onSelect = e => {
|
||||
setSelected(e.target.value)
|
||||
setError(false)
|
||||
}
|
||||
|
||||
const save = blockcypher => {
|
||||
const accounts = { blockcypher }
|
||||
return saveConfig({ variables: { accounts } })
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<H4 className={error && classes.error}>Blockcypher</H4>
|
||||
<P>
|
||||
If you are enabling cash-out services, create a Blockcypher account.
|
||||
</P>
|
||||
<SupportLinkButton
|
||||
link="https://support.lamassu.is/hc/en-us/articles/115001209472-Blockcypher"
|
||||
label="Configuring Blockcypher"
|
||||
/>
|
||||
<RadioGroup
|
||||
labelClassName={classes.radioLabel}
|
||||
className={classes.radioGroup}
|
||||
options={options}
|
||||
value={selected}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
<div className={classes.mdForm}>
|
||||
{selected === 'disable' && (
|
||||
<Button
|
||||
size="lg"
|
||||
onClick={() => addData({ zeroConf: 'none', zeroConfLimit: 0 })}
|
||||
className={classes.button}>
|
||||
Continue
|
||||
</Button>
|
||||
)}
|
||||
{selected === 'enable' && (
|
||||
<FormRenderer
|
||||
value={accounts.blockcypher}
|
||||
save={save}
|
||||
elements={blockcypherSchema.elements}
|
||||
validationSchema={blockcypherSchema.getValidationSchema}
|
||||
buttonLabel={'Continue'}
|
||||
buttonClass={classes.formButton}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Blockcypher
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import { useQuery, gql } from "@apollo/client";
|
||||
import { Formik, Form, Field } from 'formik'
|
||||
import React, { useState } from 'react'
|
||||
import PromptWhenDirty from 'src/components/PromptWhenDirty'
|
||||
import { H4 } from 'src/components/typography'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import { Button } from 'src/components/buttons'
|
||||
import { RadioGroup } from 'src/components/inputs/formik'
|
||||
|
||||
import classes from './Shared.module.css'
|
||||
|
||||
const GET_CONFIG = gql`
|
||||
{
|
||||
cryptoCurrencies {
|
||||
code
|
||||
display
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const schema = Yup.object().shape({
|
||||
coin: Yup.string().required()
|
||||
})
|
||||
|
||||
const ChooseCoin = ({ addData }) => {
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const { data } = useQuery(GET_CONFIG)
|
||||
const cryptoCurrencies = data?.cryptoCurrencies ?? []
|
||||
|
||||
const onSubmit = it => {
|
||||
if (!schema.isValidSync(it)) return setError(true)
|
||||
|
||||
if (it.coin !== 'BTC') {
|
||||
return addData({ coin: it.coin, zeroConf: 'none', zeroConfLimit: 0 })
|
||||
}
|
||||
|
||||
addData(it)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<H4 className={error && classes.error}>
|
||||
Choose your first cryptocurrency
|
||||
</H4>
|
||||
|
||||
<Formik
|
||||
validateOnBlur={false}
|
||||
validateOnChange={false}
|
||||
enableReinitialize
|
||||
initialValues={{ coin: '' }}
|
||||
onSubmit={onSubmit}>
|
||||
<Form onChange={() => setError(false)}>
|
||||
<PromptWhenDirty />
|
||||
<Field
|
||||
component={RadioGroup}
|
||||
name="coin"
|
||||
labelClassName={classes.radioLabel}
|
||||
className={classes.radioGroup}
|
||||
options={cryptoCurrencies}
|
||||
/>
|
||||
{
|
||||
<Button size="lg" type="submit" className={classes.button}>
|
||||
Continue
|
||||
</Button>
|
||||
}
|
||||
</Form>
|
||||
</Formik>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChooseCoin
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
import { useQuery, useMutation, gql } from "@apollo/client";
|
||||
import { getEquivalentCode } from '@lamassu/coins/lightUtils'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
import { H4, Info3 } from 'src/components/typography'
|
||||
import FormRenderer from 'src/pages/Services/FormRenderer'
|
||||
import WarningIcon from 'src/styling/icons/warning-icon/comet.svg?react'
|
||||
|
||||
import { Button, SupportLinkButton } from 'src/components/buttons'
|
||||
import { RadioGroup } from 'src/components/inputs'
|
||||
import _schema from 'src/pages/Services/schemas'
|
||||
|
||||
import classes from './Shared.module.css'
|
||||
import { getItems } from './getItems'
|
||||
|
||||
const GET_CONFIG = gql`
|
||||
{
|
||||
accounts
|
||||
accountsConfig {
|
||||
code
|
||||
display
|
||||
class
|
||||
cryptos
|
||||
}
|
||||
cryptoCurrencies {
|
||||
code
|
||||
display
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const SAVE_ACCOUNTS = gql`
|
||||
mutation Save($accounts: JSONObject) {
|
||||
saveAccounts(accounts: $accounts)
|
||||
}
|
||||
`
|
||||
|
||||
const isConfigurable = it =>
|
||||
!R.isNil(it) && !R.contains(it)(['mock-exchange', 'no-exchange'])
|
||||
|
||||
const ChooseExchange = ({ data: currentData, addData }) => {
|
||||
const { data } = useQuery(GET_CONFIG)
|
||||
const [saveAccounts] = useMutation(SAVE_ACCOUNTS, {
|
||||
onCompleted: () => submit()
|
||||
})
|
||||
|
||||
const [selected, setSelected] = useState(null)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const schema = _schema()
|
||||
const accounts = data?.accounts ?? []
|
||||
const accountsConfig = data?.accountsConfig ?? []
|
||||
|
||||
const coin = getEquivalentCode(currentData.coin)
|
||||
const exchanges = getItems(accountsConfig, accounts, 'exchange', coin)
|
||||
|
||||
const submit = () => {
|
||||
if (!selected) return setError(true)
|
||||
addData({ exchange: selected })
|
||||
}
|
||||
|
||||
const saveExchange = name => exchange => {
|
||||
const accounts = { [name]: exchange }
|
||||
return saveAccounts({ variables: { accounts } })
|
||||
}
|
||||
|
||||
const onSelect = e => {
|
||||
setSelected(e.target.value)
|
||||
setError(false)
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className={classes.mdForm}>
|
||||
<H4 className={error && classes.error}>Choose your exchange</H4>
|
||||
<RadioGroup
|
||||
labelClassName={classes.radioLabel}
|
||||
className={classes.radioGroup}
|
||||
options={R.union(exchanges.filled, exchanges.unfilled)}
|
||||
value={selected}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
{!isConfigurable(selected) && (
|
||||
<Button size="lg" onClick={submit} className={classes.button}>
|
||||
Continue
|
||||
</Button>
|
||||
)}
|
||||
{isConfigurable(selected) && (
|
||||
<>
|
||||
<div className={classes.infoMessage}>
|
||||
<WarningIcon />
|
||||
<Info3>
|
||||
Make sure you set up {schema[selected].name} to enter the
|
||||
necessary information below. Please follow the instructions on our
|
||||
support page if you haven’t.
|
||||
</Info3>
|
||||
</div>
|
||||
<SupportLinkButton
|
||||
link={supportArticles[selected]}
|
||||
label={`${schema[selected].name} trading`}
|
||||
/>
|
||||
|
||||
<H4 noMargin>Enter exchange information</H4>
|
||||
<FormRenderer
|
||||
value={accounts[selected]}
|
||||
save={saveExchange(selected)}
|
||||
elements={schema[selected].elements}
|
||||
validationSchema={schema[selected].getValidationSchema(accounts[selected])}
|
||||
buttonLabel={'Continue'}
|
||||
buttonClass={classes.formButton}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChooseExchange
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { useQuery, gql } from "@apollo/client";
|
||||
import { getEquivalentCode } from '@lamassu/coins/lightUtils'
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
import { H4 } from 'src/components/typography'
|
||||
|
||||
import { Button } from 'src/components/buttons'
|
||||
import { RadioGroup } from 'src/components/inputs'
|
||||
|
||||
import classes from './Shared.module.css'
|
||||
import { getItems } from './getItems'
|
||||
|
||||
const GET_CONFIG = gql`
|
||||
{
|
||||
accountsConfig {
|
||||
code
|
||||
display
|
||||
class
|
||||
cryptos
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const ChooseTicker = ({ data: currentData, addData }) => {
|
||||
const { data } = useQuery(GET_CONFIG)
|
||||
|
||||
const [selected, setSelected] = useState(null)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const accounts = data?.accounts ?? []
|
||||
const accountsConfig = data?.accountsConfig ?? []
|
||||
|
||||
const coin = getEquivalentCode(currentData.coin)
|
||||
const tickers = getItems(accountsConfig, accounts, 'ticker', coin)
|
||||
|
||||
const submit = () => {
|
||||
if (!selected) return setError(true)
|
||||
addData({ ticker: selected })
|
||||
}
|
||||
|
||||
const onSelect = e => {
|
||||
setSelected(e.target.value)
|
||||
setError(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.mdForm}>
|
||||
<H4 className={error && classes.error}>Choose your ticker</H4>
|
||||
<RadioGroup
|
||||
labelClassName={classes.radioLabel}
|
||||
className={classes.radioGroup}
|
||||
options={R.union(tickers.filled, tickers.unfilled)}
|
||||
value={selected}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
<Button size="lg" onClick={submit} className={classes.button}>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChooseTicker
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
import { useQuery, useMutation, gql } from "@apollo/client";
|
||||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
import { H4, Info3 } from 'src/components/typography'
|
||||
import FormRenderer from 'src/pages/Services/FormRenderer'
|
||||
import WarningIcon from 'src/styling/icons/warning-icon/comet.svg?react'
|
||||
|
||||
import { Button, SupportLinkButton } from 'src/components/buttons'
|
||||
import { RadioGroup } from 'src/components/inputs'
|
||||
import _schema from 'src/pages/Services/schemas'
|
||||
import bitgo from 'src/pages/Services/schemas/singlebitgo'
|
||||
|
||||
import classes from './Shared.module.css'
|
||||
import { getItems } from './getItems'
|
||||
|
||||
const GET_CONFIG = gql`
|
||||
{
|
||||
accounts
|
||||
accountsConfig {
|
||||
code
|
||||
display
|
||||
class
|
||||
cryptos
|
||||
}
|
||||
cryptoCurrencies {
|
||||
code
|
||||
display
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const SAVE_ACCOUNTS = gql`
|
||||
mutation Save($accounts: JSONObject) {
|
||||
saveAccounts(accounts: $accounts)
|
||||
}
|
||||
`
|
||||
|
||||
const isConfigurable = it =>
|
||||
R.contains(it)(['infura', 'bitgo', 'trongrid', 'galoy'])
|
||||
|
||||
const isLocalHosted = it =>
|
||||
R.contains(it)([
|
||||
'bitcoind',
|
||||
'geth',
|
||||
'litecoind',
|
||||
'dashd',
|
||||
'zcashd',
|
||||
'bitcoincashd'
|
||||
])
|
||||
|
||||
const ChooseWallet = ({ data: currentData, addData }) => {
|
||||
// no need to fetch exchange config here
|
||||
const schema = _schema()
|
||||
const { data } = useQuery(GET_CONFIG)
|
||||
const [saveAccounts] = useMutation(SAVE_ACCOUNTS, {
|
||||
onCompleted: () => submit()
|
||||
})
|
||||
|
||||
const [selected, setSelected] = useState(null)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const accounts = data?.accounts ?? []
|
||||
const accountsConfig = data?.accountsConfig ?? []
|
||||
|
||||
const coin = currentData.coin
|
||||
const wallets = getItems(accountsConfig, accounts, 'wallet', coin)
|
||||
|
||||
const saveWallet = name => wallet => {
|
||||
const accounts = { [name]: wallet }
|
||||
return saveAccounts({ variables: { accounts } })
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (!selected) return setError(true)
|
||||
addData({ wallet: selected })
|
||||
}
|
||||
|
||||
const onSelect = e => {
|
||||
setSelected(e.target.value)
|
||||
setError(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.mdForm}>
|
||||
<H4 className={error && classes.error}>Choose your wallet</H4>
|
||||
<RadioGroup
|
||||
labelClassName={classes.radioLabel}
|
||||
className={classes.radioGroup}
|
||||
options={R.union(wallets.filled, wallets.unfilled)}
|
||||
value={selected}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
{isLocalHosted(selected) && (
|
||||
<>
|
||||
<div className={classes.infoMessage}>
|
||||
<WarningIcon />
|
||||
<Info3>
|
||||
To set up {selected} please read the node wallet instructions from
|
||||
our support portal.
|
||||
</Info3>
|
||||
</div>
|
||||
<SupportLinkButton
|
||||
link="https://support.lamassu.is/hc/en-us/articles/115001209552-Setting-up-your-node-wallets"
|
||||
label="Support article"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!isConfigurable(selected) && (
|
||||
<Button size="lg" onClick={submit} className={classes.button}>
|
||||
Continue
|
||||
</Button>
|
||||
)}
|
||||
{selected === 'bitgo' && (
|
||||
<>
|
||||
<div className={classes.infoMessage}>
|
||||
<WarningIcon />
|
||||
<Info3>
|
||||
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.
|
||||
</Info3>
|
||||
</div>
|
||||
<SupportLinkButton
|
||||
link="https://support.lamassu.is/hc/en-us/articles/360024455592-Setting-up-BitGo"
|
||||
label="Support article"
|
||||
/>
|
||||
<H4 noMargin>Enter wallet information</H4>
|
||||
<FormRenderer
|
||||
value={accounts.bitgo}
|
||||
save={saveWallet(selected)}
|
||||
elements={bitgo(coin).elements}
|
||||
validationSchema={bitgo(coin).validationSchema}
|
||||
buttonLabel={'Continue'}
|
||||
buttonClass={classes.formButton}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{selected === 'infura' && (
|
||||
<>
|
||||
<H4 noMargin>Enter wallet information</H4>
|
||||
<FormRenderer
|
||||
value={accounts.infura}
|
||||
save={saveWallet(selected)}
|
||||
elements={schema.infura.elements}
|
||||
validationSchema={schema.infura.getValidationSchema(
|
||||
accounts.infura
|
||||
)}
|
||||
buttonLabel={'Continue'}
|
||||
buttonClass={classes.formButton}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{selected === 'trongrid' && (
|
||||
<>
|
||||
<H4 noMargin>Enter wallet information</H4>
|
||||
<FormRenderer
|
||||
value={accounts.trongrid}
|
||||
save={saveWallet(selected)}
|
||||
elements={schema.trongrid.elements}
|
||||
validationSchema={schema.trongrid.getValidationSchema(
|
||||
accounts.trongrid
|
||||
)}
|
||||
buttonLabel={'Continue'}
|
||||
buttonClass={classes.formButton}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{selected === 'galoy' && (
|
||||
<>
|
||||
<H4 noMargin>Enter wallet information</H4>
|
||||
<FormRenderer
|
||||
value={accounts.galoy}
|
||||
save={saveWallet(selected)}
|
||||
elements={schema.galoy.elements}
|
||||
validationSchema={schema.galoy.getValidationSchema(accounts.galoy)}
|
||||
buttonLabel={'Continue'}
|
||||
buttonClass={classes.formButton}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChooseWallet
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
.radioGroup {
|
||||
flex-direction: row;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
.radioLabel {
|
||||
width: 150px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.mdForm {
|
||||
width: 385px;
|
||||
}
|
||||
|
||||
.infoMessage {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.infoMessage > p {
|
||||
width: 330px;
|
||||
margin-top: 4px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.actionButtonLink {
|
||||
text-decoration: none;
|
||||
color: var(--zodiac);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--tomato);
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.formButton {
|
||||
margin: 24px 0 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import * as R from 'ramda'
|
||||
import React, { useState } from 'react'
|
||||
import Sidebar, { Stepper } from 'src/components/layout/Sidebar'
|
||||
import TitleSection from 'src/components/layout/TitleSection'
|
||||
|
||||
import AllSet from './AllSet'
|
||||
import Blockcypher from './Blockcypher'
|
||||
import ChooseCoin from './ChooseCoin'
|
||||
import ChooseExchange from './ChooseExchange'
|
||||
import ChooseTicker from './ChooseTicker'
|
||||
import ChooseWallet from './ChooseWallet'
|
||||
|
||||
const steps = [
|
||||
{
|
||||
label: 'Choose cryptocurrency',
|
||||
component: ChooseCoin
|
||||
},
|
||||
{
|
||||
label: 'Choose wallet',
|
||||
component: ChooseWallet
|
||||
},
|
||||
{
|
||||
label: 'Choose ticker',
|
||||
component: ChooseTicker
|
||||
},
|
||||
{
|
||||
label: 'Exchange',
|
||||
component: ChooseExchange
|
||||
},
|
||||
{
|
||||
label: 'Blockcypher',
|
||||
component: Blockcypher
|
||||
},
|
||||
{
|
||||
label: 'All set',
|
||||
component: AllSet
|
||||
}
|
||||
]
|
||||
|
||||
const Wallet = ({ doContinue }) => {
|
||||
const [step, setStep] = useState(0)
|
||||
const [data, setData] = useState({})
|
||||
|
||||
const mySteps = data?.coin === 'BTC' ? steps : R.remove(4, 1, steps)
|
||||
|
||||
const Component = mySteps[step].component
|
||||
|
||||
const addData = it => {
|
||||
setData(R.merge(data, it))
|
||||
setStep(step + 1)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-[1132px] h-full mx-auto flex-1 flex flex-col">
|
||||
<div className="flex justify-between items-center">
|
||||
<TitleSection title="Wallet settings"></TitleSection>
|
||||
</div>
|
||||
<div className="flex flex-1 flex-row">
|
||||
<Sidebar>
|
||||
{mySteps.map((it, idx) => (
|
||||
<Stepper key={idx} step={step} it={it} idx={idx} steps={mySteps} />
|
||||
))}
|
||||
</Sidebar>
|
||||
<div className="ml-12">
|
||||
<Component data={data} addData={addData} doContinue={doContinue} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Wallet
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
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 schema = _schema()
|
||||
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 { getValidationSchema } = schema[code]
|
||||
return getValidationSchema(account).isValidSync(account)
|
||||
})(fConfig)
|
||||
|
||||
return { filled, unfilled }
|
||||
}
|
||||
22
packages/admin-ui/src/pages/Wizard/components/Welcome.jsx
Normal file
22
packages/admin-ui/src/pages/Wizard/components/Welcome.jsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React from 'react'
|
||||
import { H1, P } from 'src/components/typography'
|
||||
|
||||
import { Button } from 'src/components/buttons'
|
||||
|
||||
function Welcome({ doContinue }) {
|
||||
return (
|
||||
<div className="text-center flex flex-col items-center justify-center w-full h-full">
|
||||
<H1 className="text-5xl">Welcome to the Lamassu Admin</H1>
|
||||
<P className="text-2xl mb-14 text-comet">
|
||||
To get you started, we’ve put together a wizard that will
|
||||
<br />
|
||||
help set up what you need before pairing your machines.
|
||||
</P>
|
||||
<Button size="xl" onClick={doContinue}>
|
||||
Get started
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Welcome
|
||||
131
packages/admin-ui/src/pages/Wizard/helper.jsx
Normal file
131
packages/admin-ui/src/pages/Wizard/helper.jsx
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import * as R from 'ramda'
|
||||
import React from 'react'
|
||||
import { getSchema as CommissionsSchema } from 'src/pages/Commissions/helper'
|
||||
import { WalletSchema } from 'src/pages/Wallet/helper'
|
||||
|
||||
import { LocaleSchema } from 'src/pages/Locales/helper'
|
||||
import { fromNamespace, namespaces } from 'src/utils/config'
|
||||
|
||||
import Commissions from './components/Commissions'
|
||||
import Locale from './components/Locales'
|
||||
import Twilio from './components/Twilio'
|
||||
import Wallet from './components/Wallet/Wallet'
|
||||
import Welcome from './components/Welcome'
|
||||
|
||||
const getConfiguredCoins = (config, crypto) => {
|
||||
const wallet = fromNamespace(namespaces.WALLETS, config)
|
||||
return R.filter(it =>
|
||||
WalletSchema.isValidSync(fromNamespace(it.code, wallet))
|
||||
)(crypto)
|
||||
}
|
||||
|
||||
const hasValidWallet = (config, crypto) => {
|
||||
const wallet = fromNamespace(namespaces.WALLETS, config)
|
||||
const coins = R.map(it => fromNamespace(it.code, wallet))(crypto)
|
||||
|
||||
const hasValidConfig = R.compose(
|
||||
R.any(R.identity),
|
||||
R.map(it => WalletSchema.isValidSync(it))
|
||||
)(coins)
|
||||
|
||||
return hasValidConfig
|
||||
}
|
||||
|
||||
const hasValidLocale = config => {
|
||||
const locale = fromNamespace(namespaces.LOCALE, config)
|
||||
return LocaleSchema.isValidSync(locale)
|
||||
}
|
||||
|
||||
const hasValidCommissions = config => {
|
||||
const commission = fromNamespace(namespaces.COMMISSIONS, config)
|
||||
const locale = fromNamespace(namespaces.LOCALE, config)
|
||||
return CommissionsSchema(locale).isValidSync(commission)
|
||||
}
|
||||
|
||||
const getWizardStep = (config, crypto) => {
|
||||
if (!config) return 0
|
||||
|
||||
const validWallet = hasValidWallet(config, crypto)
|
||||
if (!validWallet) return 1
|
||||
|
||||
const validLocale = hasValidLocale(config)
|
||||
if (!validLocale) return 2
|
||||
|
||||
const validCommission = hasValidCommissions(config)
|
||||
if (!validCommission) return 3
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
const STEPS = [
|
||||
{
|
||||
id: 'welcome',
|
||||
Component: Welcome
|
||||
},
|
||||
{
|
||||
id: 'wallet',
|
||||
Component: Wallet,
|
||||
exImage: '/assets/wizard/fullexample.wallet.png',
|
||||
subtitle: 'Wallet settings',
|
||||
text: `Your wallet settings are the first step for this wizard.
|
||||
We'll start by setting up one of cryptocurrencies to get you up and running,
|
||||
but you can later set up as many as you want.`
|
||||
},
|
||||
{
|
||||
id: 'locale',
|
||||
Component: Locale,
|
||||
exImage: '/assets/wizard/fullexample.locale.png',
|
||||
subtitle: 'Locales',
|
||||
text: `From the Locales panel, you can define default settings
|
||||
that will be applied to all machines you add to your network later on.
|
||||
These settings may be overridden for specific machines in the Overrides section.`
|
||||
},
|
||||
{
|
||||
id: 'twilio',
|
||||
Component: Twilio,
|
||||
exImage: '/assets/wizard/fullexample.twilio.png',
|
||||
subtitle: 'Twilio (SMS service)',
|
||||
text: (
|
||||
<>
|
||||
Twilio is used for SMS operator notifications, phone number collection
|
||||
for compliance, and 1-confirmation redemptions on cash-out transactions.
|
||||
<br />
|
||||
You'll need to configure Twilio if you're offering cash-out or any
|
||||
compliance options
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'commissions',
|
||||
Component: Commissions,
|
||||
exImage: '/assets/wizard/fullexample.commissions.png',
|
||||
subtitle: 'Commissions',
|
||||
text: `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.`
|
||||
}
|
||||
// {
|
||||
// id: 'notifications',
|
||||
// Component: Notifications,
|
||||
// exImage: '/assets/wizard/fullexample.notifications.png',
|
||||
// subtitle: 'Notifications',
|
||||
// text: `Your notification settings will allow customize what notifications you
|
||||
// get and where. You can later override all default balance alerts setup
|
||||
// here.`
|
||||
// },
|
||||
// {
|
||||
// id: 'operatorInfo',
|
||||
// Component: WizardOperatorInfo,
|
||||
// exImage: '/assets/wizard/fullexample.operatorinfo.png',
|
||||
// subtitle: 'Operator info',
|
||||
// text: `Your contact information is important for your customer to be able
|
||||
// to contact you in case there’s a problem with one of your machines.
|
||||
// In this page, you also be able to set up what you want to share with
|
||||
// Coin ATM Radar and add the Terms & Services text that is displayed by your machines.`
|
||||
// }
|
||||
]
|
||||
|
||||
export { getWizardStep, STEPS, getConfiguredCoins }
|
||||
3
packages/admin-ui/src/pages/Wizard/index.js
Normal file
3
packages/admin-ui/src/pages/Wizard/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import Wizard from './Wizard'
|
||||
|
||||
export default Wizard
|
||||
Loading…
Add table
Add a link
Reference in a new issue