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:
Mauricio Navarro Miranda 2020-07-19 20:54:23 -05:00 committed by Josh Harvey
parent 92eebd630e
commit c56d4759bd
9 changed files with 1413 additions and 216 deletions

File diff suppressed because it is too large Load diff

View file

@ -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",

View file

@ -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 (

View file

@ -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
} }
} }

View file

@ -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>
) )
}) })

View file

@ -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',

View file

@ -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>
) )
}) })

View file

@ -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]]
} }
} }

View file

@ -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

Before After
Before After