lamassu-server/new-lamassu-admin/src/pages/AddMachine/AddMachine.jsx
2025-05-12 09:45:41 +01:00

276 lines
7.9 KiB
JavaScript

import { useMutation, useQuery, gql } from '@apollo/client'
import Dialog from '@mui/material/Dialog'
import DialogContent from '@mui/material/DialogContent'
import SvgIcon from '@mui/material/SvgIcon'
import IconButton from '@mui/material/IconButton'
import classnames from 'classnames'
import { Form, Formik, FastField } from 'formik'
import { QRCodeSVG as QRCode } from 'qrcode.react'
import * as R from 'ramda'
import React, { memo, useState, useEffect, useRef } from 'react'
import Title from 'src/components/Title'
import Sidebar from 'src/components/layout/Sidebar'
import { Info2, P } from 'src/components/typography'
import CameraIcon from 'src/styling/icons/ID/photo/zodiac.svg?react'
import CloseIcon from 'src/styling/icons/action/close/zodiac.svg?react'
import CompleteStageIconSpring from 'src/styling/icons/stage/spring/complete.svg?react'
import CompleteStageIconZodiac from 'src/styling/icons/stage/zodiac/complete.svg?react'
import CurrentStageIconZodiac from 'src/styling/icons/stage/zodiac/current.svg?react'
import EmptyStageIconZodiac from 'src/styling/icons/stage/zodiac/empty.svg?react'
import WarningIcon from 'src/styling/icons/warning-icon/comet.svg?react'
import * as Yup from 'yup'
import { Button } from 'src/components/buttons'
import { TextInput } from 'src/components/inputs/formik'
import { primaryColor } from 'src/styling/variables'
const SAVE_CONFIG = gql`
mutation createPairingTotem($name: String!) {
createPairingTotem(name: $name)
}
`
const GET_MACHINES = gql`
{
machines {
name
deviceId
}
}
`
const getSize = R.compose(R.length, R.pathOr([], ['machines']))
const QrCodeComponent = ({ qrCode, name, count, onPaired }) => {
const timeout = useRef(null)
const CLOSE_SCREEN_TIMEOUT = 2000
const { data } = useQuery(GET_MACHINES, { pollInterval: 10000 })
useEffect(() => {
return () => {
if (timeout.current) {
clearTimeout(timeout.current)
}
}
}, [])
const addedMachine = data?.machines?.find(m => m.name === name)
const hasNewMachine = getSize(data) > count && addedMachine
if (hasNewMachine) {
timeout.current = setTimeout(
() => onPaired(addedMachine),
CLOSE_SCREEN_TIMEOUT
)
}
return (
<>
<Info2>Scan QR code with your new cryptomat</Info2>
<div className="flex gap-20 pt-6">
<div
className="bg-white p-1 rounded-2xl border-solid border-zodiac border-5"
data-cy={qrCode}>
<QRCode
size={280}
fgColor={primaryColor}
marginSize={3}
value={qrCode}
/>
<div className="flex items-center mb-5 ml-5">
<CameraIcon />
<P noMargin className="ml-3">
Snap a picture and scan
</P>
</div>
</div>
<div className="max-w-100">
<div className="flex gap-4 mb-4">
<div>
<WarningIcon />
</div>
<P noMargin>
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>
{hasNewMachine && (
<div className="bg-spring3 flex gap-4 p-2">
<div className="flex items-center">
<CompleteStageIconSpring />
</div>
<Info2 className="text-spring2 m-0">
Machine has been successfully paired!
</Info2>
</div>
)}
</div>
</div>
</>
)
}
const initialValues = {
name: ''
}
const validationSchema = Yup.object().shape({
name: Yup.string()
.required('Machine name is required.')
.max(50)
.test(
'unique-name',
'Machine name is already in use.',
(value, context) =>
!R.includes(
R.toLower(value),
R.map(R.toLower, context.options.context.machineNames)
)
)
})
const MachineNameComponent = ({ nextStep, setQrCode, setName }) => {
const [register] = useMutation(SAVE_CONFIG, {
onCompleted: ({ createPairingTotem }) => {
if (process.env.NODE_ENV === 'development') {
console.log(`totem: "${createPairingTotem}" `)
}
setQrCode(createPairingTotem)
nextStep()
},
onError: e => console.log(e)
})
const { data } = useQuery(GET_MACHINES)
const machineNames = R.map(R.prop('name'), data?.machines || {})
const uniqueNameValidator = value => {
try {
validationSchema.validateSync(value, {
context: { machineNames: machineNames }
})
} catch (error) {
return error
}
}
return (
<>
<Info2 className="mb-6">Machine Name (ex: Coffee shop 01)</Info2>
<Formik
validateOnBlur={false}
validateOnChange={false}
initialValues={initialValues}
validate={uniqueNameValidator}
onSubmit={({ name }) => {
setName(name)
register({ variables: { name } })
}}>
{({ errors }) => (
<Form>
<div>
<FastField
name="name"
label="Enter machine name"
component={TextInput}
/>
</div>
{errors && <P className="text-tomato">{errors.message}</P>}
<div className="mt-16">
<Button type="submit">Submit</Button>
</div>
</Form>
)}
</Formik>
</>
)
}
const steps = [
{
label: 'Machine name',
component: MachineNameComponent
},
{
label: 'Scan QR code',
component: QrCodeComponent
}
]
const renderStepper = (step, it, idx) => {
const active = step === idx
const past = idx < step
const future = idx > step
return (
<div className="flex relative my-3">
<span
className={classnames({
'mr-6 text-comet': true,
'text-zodiac font-bold': active,
'text-zodiac': past
})}>
{it.label}
</span>
{active && <CurrentStageIconZodiac />}
{past && <CompleteStageIconZodiac />}
{future && <EmptyStageIconZodiac />}
{idx < steps.length - 1 && (
<div
className={classnames({
'absolute h-7 w-px border border-comet border-solid right-2 top-[18px]': true,
'border-zodiac': past
})}></div>
)}
</div>
)
}
const AddMachine = memo(({ close, onPaired }) => {
const { data } = useQuery(GET_MACHINES)
const [qrCode, setQrCode] = useState('')
const [name, setName] = useState('')
const [step, setStep] = useState(0)
const count = getSize(data)
const Component = steps[step].component
return (
<div>
<Dialog fullScreen open={true} aria-labelledby="form-dialog-title">
<DialogContent className="p-0 pt-5 bg-ghost">
<div
className=" mx-auto flex flex-col flex-col flex-col flex-col
flex-col flex-col flex-col flex-col w-[1200px] h-full ">
<div className="flex items-center justify-between">
<Title>Add Machine</Title>
<IconButton onClick={close} size="large">
<SvgIcon color="error">
<CloseIcon />
</SvgIcon>
</IconButton>
</div>
<div className="flex flex-1">
<Sidebar>
{steps.map((it, idx) => renderStepper(step, it, idx))}
</Sidebar>
<div className="px-12 flex-1">
<Component
nextStep={() => setStep(1)}
count={count}
onPaired={onPaired}
qrCode={qrCode}
setQrCode={setQrCode}
name={name}
setName={setName}
/>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</div>
)
})
export default AddMachine