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.qrTextIcon}> <div className={classes.qrCodeWrapper}>
<WarningIcon /> <div className={classes.qrTextIcon}>
<WarningIcon />
</div>
<P className={classes.qrText}>
To pair the machine you need scan the QR code with your machine.
To do this either snap a picture of this QR code or download it
through the button above and scan it with the scanning bay on your
machine.
</P>
</div> </div>
<P className={classes.qrText}> {paired && <Info3> Machine has been successfully paired</Info3>}
To pair the machine you need scan the QR code with your machine. To
do this either snap a picture of this QR code or download it through
the button above and scan it with the scanning bay on your machine.
</P>
</div> </div>
</div> </div>
{doneButton && (
<div className={classes.button}>
<Button type="submit" onClick={close}>
Done
</Button>
</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,47 +151,80 @@ 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 (
<Dialog <div>
fullScreen <Dialog
className={classes.dialog} fullScreen
open={true} className={classes.dialog}
aria-labelledby="form-dialog-title"> open={true}
<DialogContent className={classes.dialog}> aria-labelledby="form-dialog-title">
<div className={classes.wrapper}> <DialogContent className={classes.dialog}>
<div className={classes.headerDiv}> <div className={classes.wrapper}>
<Title>Add Machine</Title> <div className={classes.headerDiv}>
<IconButton disableRipple={true} onClick={close}> <Title>Add Machine</Title>
<SvgIcon color="error"> <IconButton disableRipple={true} onClick={close}>
<CloseIcon /> <SvgIcon color="error">
</SvgIcon> <CloseIcon />
</IconButton> </SvgIcon>
</div> </IconButton>
<div className={classes.contentDiv}> </div>
<Sidebar> <div className={classes.contentDiv}>
{steps.map((it, idx) => ( <Sidebar>
<Stepper step={step} it={it} idx={idx} steps={steps} /> {steps.map((it, idx) => renderStepper(step, it, idx, classes))}
))} </Sidebar>
</Sidebar> <div className={classes.contentWrapper}>
<div className={classes.contentWrapper}> <Component
<Component classes={classes}
classes={classes} nextStep={() => inc(1)}
qrCode={qrCode} close={close}
close={close} onPaired={onPaired}
setQrCode={setQrCode} {...{ payload, setPayload }}
nextStep={() => setStep(step + 1)} />
/> </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