feat: close add machine wizard on success
fix: remove done button fix: auto close on success fix: show success message fix: move add machine to main nav fix: steps overlap fix: no console logs, simplify some lines fix: remove spinner and toast fix: show info3 message on pairing success fix: eslint fix: consider the machine name while verifying if the new machine is paired
This commit is contained in:
parent
92eebd630e
commit
c56d4759bd
9 changed files with 1413 additions and 216 deletions
1343
new-lamassu-admin/package-lock.json
generated
1343
new-lamassu-admin/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -32,6 +32,7 @@
|
||||||
"react-dom": "^16.10.2",
|
"react-dom": "^16.10.2",
|
||||||
"react-number-format": "^4.4.1",
|
"react-number-format": "^4.4.1",
|
||||||
"react-router-dom": "5.1.2",
|
"react-router-dom": "5.1.2",
|
||||||
|
"react-use": "15.3.2",
|
||||||
"react-virtualized": "^9.21.2",
|
"react-virtualized": "^9.21.2",
|
||||||
"sanctuary": "^2.0.1",
|
"sanctuary": "^2.0.1",
|
||||||
"uuid": "^7.0.2",
|
"uuid": "^7.0.2",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ const Link = memo(
|
||||||
[classes.link]: true,
|
[classes.link]: true,
|
||||||
[classes.primary]: color === 'primary',
|
[classes.primary]: color === 'primary',
|
||||||
[classes.secondary]: color === 'secondary',
|
[classes.secondary]: color === 'secondary',
|
||||||
[classes.noColor]: color === 'noColor'
|
[classes.noColor]: color === 'noColor',
|
||||||
|
[classes.action]: color === 'action'
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import typographyStyles from 'src/components/typography/styles'
|
||||||
import {
|
import {
|
||||||
white,
|
white,
|
||||||
linkPrimaryColor,
|
linkPrimaryColor,
|
||||||
linkSecondaryColor
|
linkSecondaryColor,
|
||||||
|
zircon
|
||||||
} from 'src/styling/variables'
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
const { h4 } = typographyStyles
|
const { h4 } = typographyStyles
|
||||||
|
|
@ -38,5 +39,9 @@ export default {
|
||||||
},
|
},
|
||||||
noColor: {
|
noColor: {
|
||||||
extend: color(white)
|
extend: color(white)
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
extend: color(linkPrimaryColor),
|
||||||
|
color: zircon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import styles from './Header.styles'
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const Subheader = ({ item, classes }) => {
|
const Subheader = ({ item, classes }) => {
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.subheader}>
|
<div className={classes.subheader}>
|
||||||
<div className={classes.content}>
|
<div className={classes.content}>
|
||||||
|
|
@ -31,21 +30,18 @@ const Subheader = ({ item, classes }) => {
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div className={classes.addMachine}>
|
|
||||||
<Link color="primary" onClick={() => setOpen(true)}>
|
|
||||||
Add Machine
|
|
||||||
</Link>
|
|
||||||
{open && <AddMachine close={() => setOpen(false)} />}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = memo(({ tree }) => {
|
const Header = memo(({ tree }) => {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
const [active, setActive] = useState()
|
const [active, setActive] = useState()
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
|
const onPaired = _name => {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header>
|
<header>
|
||||||
<div className={classes.header}>
|
<div className={classes.header}>
|
||||||
|
|
@ -75,12 +71,16 @@ const Header = memo(({ tree }) => {
|
||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
<Link color="action" onClick={() => setOpen(true)}>
|
||||||
|
Add Machine
|
||||||
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{active && active.children && (
|
{active && active.children && (
|
||||||
<Subheader item={active} classes={classes} />
|
<Subheader item={active} classes={classes} />
|
||||||
)}
|
)}
|
||||||
|
{open && <AddMachine close={() => setOpen(false)} onPaired={onPaired} />}
|
||||||
</header>
|
</header>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,10 @@ export default {
|
||||||
margin: '0 auto'
|
margin: '0 auto'
|
||||||
},
|
},
|
||||||
nav: {
|
nav: {
|
||||||
flex: 1
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between'
|
||||||
},
|
},
|
||||||
ul: {
|
ul: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
import { useMutation } from '@apollo/react-hooks'
|
import { useMutation, useQuery } from '@apollo/react-hooks'
|
||||||
import { Dialog, DialogContent, SvgIcon, IconButton } from '@material-ui/core'
|
import { Dialog, DialogContent, SvgIcon, IconButton } from '@material-ui/core'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import { Form, Formik, FastField } from 'formik'
|
import { Form, Formik, FastField } from 'formik'
|
||||||
import gql from 'graphql-tag'
|
import gql from 'graphql-tag'
|
||||||
import QRCode from 'qrcode.react'
|
import QRCode from 'qrcode.react'
|
||||||
import React, { memo, useState } from 'react'
|
import * as R from 'ramda'
|
||||||
|
import React, { memo, useEffect } from 'react'
|
||||||
|
import { useSetState, useCounter, useBoolean, useLifecycles } from 'react-use'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
import Title from 'src/components/Title'
|
import Title from 'src/components/Title'
|
||||||
import { Button } from 'src/components/buttons'
|
import { Button } from 'src/components/buttons'
|
||||||
import { TextInput } from 'src/components/inputs/formik'
|
import { TextInput } from 'src/components/inputs/formik'
|
||||||
import Sidebar, { Stepper } from 'src/components/layout/Sidebar'
|
import Sidebar from 'src/components/layout/Sidebar'
|
||||||
import { Info2, P } from 'src/components/typography'
|
import { Info2, P, Info3 } from 'src/components/typography'
|
||||||
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
import { ReactComponent as CloseIcon } from 'src/styling/icons/action/close/zodiac.svg'
|
||||||
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg'
|
import { ReactComponent as WarningIcon } from 'src/styling/icons/warning-icon/comet.svg'
|
||||||
import { primaryColor } from 'src/styling/variables'
|
import { primaryColor } from 'src/styling/variables'
|
||||||
|
|
@ -23,12 +25,44 @@ const SAVE_CONFIG = gql`
|
||||||
createPairingTotem(name: $name)
|
createPairingTotem(name: $name)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
const GET_MACHINES = gql`
|
||||||
|
{
|
||||||
|
machines {
|
||||||
|
name
|
||||||
|
deviceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const useStyles = makeStyles(styles)
|
const useStyles = makeStyles(styles)
|
||||||
|
|
||||||
const QrCodeComponent = ({ classes, qrCode, close }) => {
|
const getSize = R.compose(R.length, R.pathOr([], ['machines']))
|
||||||
const [doneButton, setDoneButton] = useState(null)
|
|
||||||
setTimeout(() => setDoneButton(true), 2000)
|
function usePairDevice({ name, count }) {
|
||||||
|
const [on, toggle] = useBoolean(false)
|
||||||
|
const { data, stopPolling, startPolling } = useQuery(GET_MACHINES)
|
||||||
|
const size = getSize(data)
|
||||||
|
|
||||||
|
useLifecycles(() => startPolling(10000), stopPolling)
|
||||||
|
useEffect(() =>
|
||||||
|
size > count && data?.machines?.some(m => m.name === name)
|
||||||
|
? toggle(true)
|
||||||
|
: undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
return [on]
|
||||||
|
}
|
||||||
|
|
||||||
|
const QrCodeComponent = ({ classes, payload, close, onPaired }) => {
|
||||||
|
const { qrcode, count, name } = payload
|
||||||
|
const [paired] = usePairDevice({ name, count })
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (paired) {
|
||||||
|
onPaired(name)
|
||||||
|
setTimeout(() => close(), 3000)
|
||||||
|
}
|
||||||
|
}, [close, name, onPaired, paired])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -37,26 +71,23 @@ const QrCodeComponent = ({ classes, qrCode, close }) => {
|
||||||
</Info2>
|
</Info2>
|
||||||
<div className={classes.qrCodeWrapper}>
|
<div className={classes.qrCodeWrapper}>
|
||||||
<div>
|
<div>
|
||||||
<QRCode size={240} fgColor={primaryColor} value={qrCode} />
|
<QRCode size={240} fgColor={primaryColor} value={qrcode} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.qrTextWrapper}>
|
<div className={classes.qrTextWrapper}>
|
||||||
|
<div className={classes.qrCodeWrapper}>
|
||||||
<div className={classes.qrTextIcon}>
|
<div className={classes.qrTextIcon}>
|
||||||
<WarningIcon />
|
<WarningIcon />
|
||||||
</div>
|
</div>
|
||||||
<P className={classes.qrText}>
|
<P className={classes.qrText}>
|
||||||
To pair the machine you need scan the QR code with your machine. To
|
To pair the machine you need scan the QR code with your machine.
|
||||||
do this either snap a picture of this QR code or download it through
|
To do this either snap a picture of this QR code or download it
|
||||||
the button above and scan it with the scanning bay on your machine.
|
through the button above and scan it with the scanning bay on your
|
||||||
|
machine.
|
||||||
</P>
|
</P>
|
||||||
</div>
|
</div>
|
||||||
|
{paired && <Info3>✓ Machine has been successfully paired</Info3>}
|
||||||
</div>
|
</div>
|
||||||
{doneButton && (
|
|
||||||
<div className={classes.button}>
|
|
||||||
<Button type="submit" onClick={close}>
|
|
||||||
Done
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -71,13 +102,10 @@ const validationSchema = Yup.object().shape({
|
||||||
.max(50, 'Too long')
|
.max(50, 'Too long')
|
||||||
})
|
})
|
||||||
|
|
||||||
const MachineNameComponent = ({ nextStep, classes, setQrCode }) => {
|
const MachineNameComponent = ({ nextStep, classes, setPayload }) => {
|
||||||
const [register] = useMutation(SAVE_CONFIG, {
|
const [register] = useMutation(SAVE_CONFIG, {
|
||||||
onCompleted: data => {
|
onCompleted: ({ createPairingTotem }) => {
|
||||||
if (process.env.NODE_ENV === 'development') {
|
setPayload({ qrcode: createPairingTotem })
|
||||||
console.log('totem: ', data.createPairingTotem)
|
|
||||||
}
|
|
||||||
setQrCode(data.createPairingTotem)
|
|
||||||
nextStep()
|
nextStep()
|
||||||
},
|
},
|
||||||
onError: e => console.log(e)
|
onError: e => console.log(e)
|
||||||
|
|
@ -92,6 +120,7 @@ const MachineNameComponent = ({ nextStep, classes, setQrCode }) => {
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={({ name }) => {
|
onSubmit={({ name }) => {
|
||||||
|
setPayload({ name })
|
||||||
register({ variables: { name } })
|
register({ variables: { name } })
|
||||||
}}>
|
}}>
|
||||||
<Form className={classes.form}>
|
<Form className={classes.form}>
|
||||||
|
|
@ -122,13 +151,47 @@ const steps = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const AddMachine = memo(({ close }) => {
|
const renderStepper = (step, it, idx, classes) => {
|
||||||
|
const active = step === idx
|
||||||
|
const past = idx < step
|
||||||
|
const future = idx > step
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.item}>
|
||||||
|
<span
|
||||||
|
className={classnames({
|
||||||
|
[classes.itemText]: true,
|
||||||
|
[classes.itemTextActive]: active,
|
||||||
|
[classes.itemTextPast]: past
|
||||||
|
})}>
|
||||||
|
{it.label}
|
||||||
|
</span>
|
||||||
|
{active && <CurrentStageIconZodiac />}
|
||||||
|
{past && <CompleteStageIconZodiac />}
|
||||||
|
{future && <EmptyStageIconZodiac />}
|
||||||
|
{idx < steps.length - 1 && (
|
||||||
|
<div
|
||||||
|
className={classnames({
|
||||||
|
[classes.stepperPath]: true,
|
||||||
|
[classes.stepperPast]: past
|
||||||
|
})}></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddMachine = memo(({ close, onPaired }) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [qrCode, setQrCode] = useState(null)
|
const { data } = useQuery(GET_MACHINES)
|
||||||
const [step, setStep] = useState(0)
|
const [payload, setPayload] = useSetState({ qrcode: '', name: '', count: 0 })
|
||||||
|
const [step, { inc }] = useCounter(0, steps.length, 0)
|
||||||
|
const count = getSize(data)
|
||||||
|
|
||||||
|
useEffect(() => setPayload({ count }), [count, setPayload])
|
||||||
|
|
||||||
const Component = steps[step].component
|
const Component = steps[step].component
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
<Dialog
|
<Dialog
|
||||||
fullScreen
|
fullScreen
|
||||||
className={classes.dialog}
|
className={classes.dialog}
|
||||||
|
|
@ -146,23 +209,22 @@ const AddMachine = memo(({ close }) => {
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.contentDiv}>
|
<div className={classes.contentDiv}>
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
{steps.map((it, idx) => (
|
{steps.map((it, idx) => renderStepper(step, it, idx, classes))}
|
||||||
<Stepper step={step} it={it} idx={idx} steps={steps} />
|
|
||||||
))}
|
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
<div className={classes.contentWrapper}>
|
<div className={classes.contentWrapper}>
|
||||||
<Component
|
<Component
|
||||||
classes={classes}
|
classes={classes}
|
||||||
qrCode={qrCode}
|
nextStep={() => inc(1)}
|
||||||
close={close}
|
close={close}
|
||||||
setQrCode={setQrCode}
|
onPaired={onPaired}
|
||||||
nextStep={() => setStep(step + 1)}
|
{...{ payload, setPayload }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
import { backgroundColor, mainWidth } from 'src/styling/variables'
|
import typographyStyles from 'src/components/typography/styles'
|
||||||
|
import {
|
||||||
|
placeholderColor,
|
||||||
|
backgroundColor,
|
||||||
|
primaryColor,
|
||||||
|
mainWidth
|
||||||
|
} from 'src/styling/variables'
|
||||||
|
|
||||||
|
const { tl2, p } = typographyStyles
|
||||||
|
|
||||||
const fill = '100%'
|
const fill = '100%'
|
||||||
const flexDirection = 'column'
|
const flexDirection = 'column'
|
||||||
|
|
@ -50,13 +58,43 @@ const styles = {
|
||||||
qrTextWrapper: {
|
qrTextWrapper: {
|
||||||
width: 381,
|
width: 381,
|
||||||
marginLeft: 80,
|
marginLeft: 80,
|
||||||
display: 'flex'
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center'
|
||||||
},
|
},
|
||||||
qrTextIcon: {
|
qrTextIcon: {
|
||||||
marginRight: 16
|
marginRight: 16
|
||||||
},
|
},
|
||||||
qrText: {
|
qrText: {
|
||||||
marginTop: 0
|
marginTop: 0
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
position: 'relative',
|
||||||
|
margin: '12px 0 12px 0',
|
||||||
|
display: 'flex'
|
||||||
|
},
|
||||||
|
itemText: {
|
||||||
|
extend: p,
|
||||||
|
color: placeholderColor,
|
||||||
|
marginRight: 24
|
||||||
|
},
|
||||||
|
itemTextActive: {
|
||||||
|
extend: tl2,
|
||||||
|
color: primaryColor
|
||||||
|
},
|
||||||
|
itemTextPast: {
|
||||||
|
color: primaryColor
|
||||||
|
},
|
||||||
|
stepperPath: {
|
||||||
|
position: 'absolute',
|
||||||
|
height: 25,
|
||||||
|
width: 1,
|
||||||
|
border: [[1, 'solid', placeholderColor]],
|
||||||
|
right: 8,
|
||||||
|
top: 18
|
||||||
|
},
|
||||||
|
stepperPast: {
|
||||||
|
border: [[1, 'solid', primaryColor]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
|
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
<g id="icon/stage/zodiac/empty" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
<g id="icon/stage/zodiac/empty" stroke="none" stroke-width="2" fill="none" fill-rule="evenodd">
|
||||||
<circle id="Oval-Copy-6" stroke="#5F668A" transform="translate(9.000000, 9.000000) rotate(-270.000000) translate(-9.000000, -9.000000) " cx="9" cy="9" r="8"></circle>
|
<circle id="Oval-Copy-6" stroke="#5F668A" transform="translate(9.000000, 9.000000) rotate(-270.000000) translate(-9.000000, -9.000000) " cx="9" cy="9" r="8"></circle>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 577 B After Width: | Height: | Size: 577 B |
Loading…
Add table
Add a link
Reference in a new issue