chore: prettier config

husky needs to be delegated to v11 since it breaks with node 14 + 22
combo
This commit is contained in:
Rafael 2024-12-03 09:33:06 +00:00
parent dbca0c8a8c
commit d3c3de66fe
65 changed files with 1542 additions and 22553 deletions

View file

@ -0,0 +1,8 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"arrowParens": "avoid",
"bracketSameLine": true
}

View file

@ -2,6 +2,7 @@ import globals from 'globals'
import pluginJs from '@eslint/js' import pluginJs from '@eslint/js'
import pluginReact from 'eslint-plugin-react' import pluginReact from 'eslint-plugin-react'
import reactCompiler from 'eslint-plugin-react-compiler' import reactCompiler from 'eslint-plugin-react-compiler'
import eslintConfigPrettier from 'eslint-config-prettier'
/** @type {import('eslint').Linter.Config[]} */ /** @type {import('eslint').Linter.Config[]} */
export default [ export default [
@ -17,7 +18,7 @@ export default [
settings: { settings: {
react: { react: {
version: '16' version: '16'
}, }
}, },
plugins: { plugins: {
'react-compiler': reactCompiler 'react-compiler': reactCompiler
@ -31,7 +32,8 @@ export default [
'react/prop-types': 'off', 'react/prop-types': 'off',
'react/display-name': 'off', 'react/display-name': 'off',
'react/no-unescaped-entities': 'off', 'react/no-unescaped-entities': 'off',
'react-compiler/react-compiler': 'warn', 'react-compiler/react-compiler': 'warn'
} }
} },
] eslintConfigPrettier
]

File diff suppressed because it is too large Load diff

View file

@ -56,26 +56,19 @@
"@vitejs/plugin-react-swc": "^3.7.2", "@vitejs/plugin-react-swc": "^3.7.2",
"esbuild-plugin-react-virtualized": "^1.0.4", "esbuild-plugin-react-virtualized": "^1.0.4",
"eslint": "^9.16.0", "eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.37.2", "eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124", "eslint-plugin-react-compiler": "^19.0.0-beta-df7b47d-20241124",
"globals": "^15.13.0", "globals": "^15.13.0",
"husky": "^3.1.0", "lint-staged": "^15.2.10",
"lint-staged": "^9.5.0", "prettier": "3.4.1",
"react-scripts": "4.0.0",
"vite": "^6.0.1", "vite": "^6.0.1",
"vite-plugin-svgr": "^4.3.0" "vite-plugin-svgr": "^4.3.0"
}, },
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
"eslint --fix",
"git add"
]
},
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview"
"fix": "eslint --fix --ext .js,.md,.json src/"
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
@ -88,5 +81,9 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"lint-staged": {
"*.{js,jsx,md,json}": "eslint --cache --fix",
"*.{js,jsx,css,md,json}": "prettier --write"
} }
} }

View file

@ -115,15 +115,10 @@ const Main = () => {
)} )}
<main className={classes.wrapper}> <main className={classes.wrapper}>
{sidebar && !is404 && wizardTested && ( {sidebar && !is404 && wizardTested && (
<Slide <Slide direction="left" in={true} mountOnEnter unmountOnExit>
direction="left" <div>
in={true} <TitleSection title={parent.title}></TitleSection>
mountOnEnter </div>
unmountOnExit
>
<div>
<TitleSection title={parent.title}></TitleSection>
</div>
</Slide> </Slide>
)} )}

View file

@ -1,98 +1,98 @@
import { import {
Dialog, Dialog,
DialogActions, DialogActions,
DialogContent, DialogContent,
makeStyles makeStyles
} from '@material-ui/core' } from '@material-ui/core'
import React from 'react' import React from 'react'
import { H4, P } from 'src/components/typography' import { H4, P } from 'src/components/typography'
import CloseIcon from 'src/styling/icons/action/close/zodiac.svg?react' import CloseIcon from 'src/styling/icons/action/close/zodiac.svg?react'
import { Button, IconButton } from 'src/components/buttons' import { Button, IconButton } from 'src/components/buttons'
import { spacer } from 'src/styling/variables' import { spacer } from 'src/styling/variables'
import ErrorMessage from './ErrorMessage' import ErrorMessage from './ErrorMessage'
const useStyles = makeStyles({ const useStyles = makeStyles({
content: { content: {
width: 434, width: 434,
padding: spacer * 2, padding: spacer * 2,
paddingRight: spacer * 3.5 paddingRight: spacer * 3.5
}, },
titleSection: { titleSection: {
padding: spacer * 2, padding: spacer * 2,
paddingRight: spacer * 1.5, paddingRight: spacer * 1.5,
display: 'flex', display: 'flex',
justifyContent: 'space-between', justifyContent: 'space-between',
margin: 0 margin: 0
}, },
actions: { actions: {
padding: spacer * 4, padding: spacer * 4,
paddingTop: spacer * 2 paddingTop: spacer * 2
}, },
title: { title: {
margin: 0 margin: 0
}, },
closeButton: { closeButton: {
padding: 0, padding: 0,
marginTop: -(spacer / 2) marginTop: -(spacer / 2)
} }
}) })
export const DialogTitle = ({ children, close }) => { export const DialogTitle = ({ children, close }) => {
const classes = useStyles() const classes = useStyles()
return ( return (
<div className={classes.titleSection}> <div className={classes.titleSection}>
{children} {children}
{close && ( {close && (
<IconButton <IconButton
size={16} size={16}
aria-label="close" aria-label="close"
onClick={close} onClick={close}
className={classes.closeButton}> className={classes.closeButton}>
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
)} )}
</div> </div>
) )
} }
export const DeleteDialog = ({ export const DeleteDialog = ({
title = 'Confirm Delete', title = 'Confirm Delete',
open = false, open = false,
onConfirmed, onConfirmed,
onDismissed, onDismissed,
item = 'item', item = 'item',
confirmationMessage = `Are you sure you want to delete this ${item}?`, confirmationMessage = `Are you sure you want to delete this ${item}?`,
extraMessage, extraMessage,
errorMessage = '' errorMessage = ''
}) => { }) => {
const classes = useStyles() const classes = useStyles()
return ( return (
<Dialog open={open} aria-labelledby="form-dialog-title"> <Dialog open={open} aria-labelledby="form-dialog-title">
<DialogTitle close={() => onDismissed()}> <DialogTitle close={() => onDismissed()}>
<H4 className={classes.title}>{title}</H4> <H4 className={classes.title}>{title}</H4>
</DialogTitle> </DialogTitle>
{errorMessage && ( {errorMessage && (
<DialogTitle> <DialogTitle>
<ErrorMessage> <ErrorMessage>
{errorMessage.split(':').map(error => ( {errorMessage.split(':').map(error => (
<> <>
{error} {error}
<br /> <br />
</> </>
))} ))}
</ErrorMessage> </ErrorMessage>
</DialogTitle> </DialogTitle>
)} )}
<DialogContent className={classes.content}> <DialogContent className={classes.content}>
{confirmationMessage && <P>{confirmationMessage}</P>} {confirmationMessage && <P>{confirmationMessage}</P>}
{extraMessage} {extraMessage}
</DialogContent> </DialogContent>
<DialogActions className={classes.actions}> <DialogActions className={classes.actions}>
<Button onClick={onConfirmed}>Confirm</Button> <Button onClick={onConfirmed}>Confirm</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
) )
} }

View file

@ -55,8 +55,8 @@ const styles = {
margin: xl margin: xl
? [[0, 0, 'auto', 'auto']] ? [[0, 0, 'auto', 'auto']]
: small : small
? [[12, 12, 'auto', 'auto']] ? [[12, 12, 'auto', 'auto']]
: [[16, 16, 'auto', 'auto']] : [[16, 16, 'auto', 'auto']]
}), }),
header: { header: {
display: 'flex' display: 'flex'

View file

@ -60,8 +60,8 @@ const NotificationRow = ({
typeDisplay && deviceName typeDisplay && deviceName
? `${typeDisplay} - ${deviceName}` ? `${typeDisplay} - ${deviceName}`
: !typeDisplay && deviceName : !typeDisplay && deviceName
? `${deviceName}` ? `${deviceName}`
: `${typeDisplay}` : `${typeDisplay}`
const iconClass = { const iconClass = {
[classes.readIcon]: read, [classes.readIcon]: read,

View file

@ -1,299 +1,299 @@
import { makeStyles } from '@material-ui/core' import { makeStyles } from '@material-ui/core'
import classnames from 'classnames' import classnames from 'classnames'
import { Field, useFormikContext } from 'formik' import { Field, useFormikContext } from 'formik'
import * as R from 'ramda' import * as R from 'ramda'
import React, { useContext, useState } from 'react' import React, { useContext, useState } from 'react'
import { DeleteDialog } from 'src/components/DeleteDialog' import { DeleteDialog } from 'src/components/DeleteDialog'
import { Td, Tr } from 'src/components/fake-table/Table' import { Td, Tr } from 'src/components/fake-table/Table'
import { Label2 } from 'src/components/typography' import { Label2 } from 'src/components/typography'
import DisabledDeleteIcon from 'src/styling/icons/action/delete/disabled.svg?react' import DisabledDeleteIcon from 'src/styling/icons/action/delete/disabled.svg?react'
import DeleteIcon from 'src/styling/icons/action/delete/enabled.svg?react' import DeleteIcon from 'src/styling/icons/action/delete/enabled.svg?react'
import DisabledEditIcon from 'src/styling/icons/action/edit/disabled.svg?react' import DisabledEditIcon from 'src/styling/icons/action/edit/disabled.svg?react'
import EditIcon from 'src/styling/icons/action/edit/enabled.svg?react' import EditIcon from 'src/styling/icons/action/edit/enabled.svg?react'
import StripesSvg from 'src/styling/icons/stripes.svg?react' import StripesSvg from 'src/styling/icons/stripes.svg?react'
import { Link, IconButton } from 'src/components/buttons' import { Link, IconButton } from 'src/components/buttons'
import { Switch } from 'src/components/inputs' import { Switch } from 'src/components/inputs'
import TableCtx from './Context' import TableCtx from './Context'
import styles from './Row.styles' import styles from './Row.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const ActionCol = ({ disabled, editing }) => { const ActionCol = ({ disabled, editing }) => {
const classes = useStyles() const classes = useStyles()
const { values, submitForm, resetForm } = useFormikContext() const { values, submitForm, resetForm } = useFormikContext()
const { const {
editWidth, editWidth,
onEdit, onEdit,
enableEdit, enableEdit,
enableDelete, enableDelete,
disableRowEdit, disableRowEdit,
onDelete, onDelete,
deleteWidth, deleteWidth,
enableToggle, enableToggle,
onToggle, onToggle,
toggleWidth, toggleWidth,
forceAdd, forceAdd,
clearError, clearError,
actionColSize, actionColSize,
error error
} = useContext(TableCtx) } = useContext(TableCtx)
const disableEdit = disabled || (disableRowEdit && disableRowEdit(values)) const disableEdit = disabled || (disableRowEdit && disableRowEdit(values))
const cancel = () => { const cancel = () => {
clearError() clearError()
resetForm() resetForm()
} }
const [deleteDialog, setDeleteDialog] = useState(false) const [deleteDialog, setDeleteDialog] = useState(false)
const onConfirmed = () => { const onConfirmed = () => {
onDelete(values.id).then(res => { onDelete(values.id).then(res => {
if (!R.isNil(res)) setDeleteDialog(false) if (!R.isNil(res)) setDeleteDialog(false)
}) })
} }
return ( return (
<> <>
{editing && ( {editing && (
<Td textAlign="center" width={actionColSize}> <Td textAlign="center" width={actionColSize}>
<Link <Link
className={classes.saveButton} className={classes.saveButton}
type="submit" type="submit"
color="primary" color="primary"
onClick={submitForm}> onClick={submitForm}>
Save Save
</Link> </Link>
{!forceAdd && ( {!forceAdd && (
<Link color="secondary" onClick={cancel}> <Link color="secondary" onClick={cancel}>
Cancel Cancel
</Link> </Link>
)} )}
</Td> </Td>
)} )}
{!editing && enableEdit && ( {!editing && enableEdit && (
<Td textAlign="center" width={editWidth}> <Td textAlign="center" width={editWidth}>
<IconButton <IconButton
disabled={disableEdit} disabled={disableEdit}
className={classes.editButton} className={classes.editButton}
onClick={() => onEdit && onEdit(values.id)}> onClick={() => onEdit && onEdit(values.id)}>
{disableEdit ? <DisabledEditIcon /> : <EditIcon />} {disableEdit ? <DisabledEditIcon /> : <EditIcon />}
</IconButton> </IconButton>
</Td> </Td>
)} )}
{!editing && enableDelete && ( {!editing && enableDelete && (
<Td textAlign="center" width={deleteWidth}> <Td textAlign="center" width={deleteWidth}>
<IconButton <IconButton
disabled={disabled} disabled={disabled}
onClick={() => { onClick={() => {
setDeleteDialog(true) setDeleteDialog(true)
}}> }}>
{disabled ? <DisabledDeleteIcon /> : <DeleteIcon />} {disabled ? <DisabledDeleteIcon /> : <DeleteIcon />}
</IconButton> </IconButton>
<DeleteDialog <DeleteDialog
open={deleteDialog} open={deleteDialog}
setDeleteDialog={setDeleteDialog} setDeleteDialog={setDeleteDialog}
onConfirmed={onConfirmed} onConfirmed={onConfirmed}
onDismissed={() => { onDismissed={() => {
setDeleteDialog(false) setDeleteDialog(false)
clearError() clearError()
}} }}
errorMessage={error} errorMessage={error}
/> />
</Td> </Td>
)} )}
{!editing && enableToggle && ( {!editing && enableToggle && (
<Td textAlign="center" width={toggleWidth}> <Td textAlign="center" width={toggleWidth}>
<Switch <Switch
checked={!!values.active} checked={!!values.active}
value={!!values.active} value={!!values.active}
disabled={disabled} disabled={disabled}
onChange={() => onToggle(values.id)} onChange={() => onToggle(values.id)}
/> />
</Td> </Td>
)} )}
</> </>
) )
} }
const ECol = ({ editing, focus, config, extraPaddingRight, extraPadding }) => { const ECol = ({ editing, focus, config, extraPaddingRight, extraPadding }) => {
const { const {
name, name,
names, names,
bypassField, bypassField,
input, input,
editable = true, editable = true,
size, size,
bold, bold,
width, width,
textAlign, textAlign,
editingAlign = textAlign, editingAlign = textAlign,
prefix, prefix,
PrefixComponent = Label2, PrefixComponent = Label2,
suffix, suffix,
SuffixComponent = Label2, SuffixComponent = Label2,
textStyle = it => {}, textStyle = it => {},
isHidden = it => false, isHidden = it => false,
view = it => it?.toString(), view = it => it?.toString(),
inputProps = {} inputProps = {}
} = config } = config
const fields = names ?? [name] const fields = names ?? [name]
const { values } = useFormikContext() const { values } = useFormikContext()
const isEditable = editable => { const isEditable = editable => {
if (typeof editable === 'function') return editable(values) if (typeof editable === 'function') return editable(values)
return editable return editable
} }
const isEditing = editing && isEditable(editable) const isEditing = editing && isEditable(editable)
const isField = !bypassField const isField = !bypassField
const classes = useStyles({ const classes = useStyles({
textAlign: isEditing ? editingAlign : textAlign, textAlign: isEditing ? editingAlign : textAlign,
size size
}) })
const innerProps = { const innerProps = {
fullWidth: true, fullWidth: true,
autoFocus: focus, autoFocus: focus,
size, size,
bold, bold,
textAlign: isEditing ? editingAlign : textAlign, textAlign: isEditing ? editingAlign : textAlign,
...inputProps ...inputProps
} }
return ( return (
<div className={classes.fields}> <div className={classes.fields}>
{fields.map((f, idx) => ( {fields.map((f, idx) => (
<Td <Td
key={idx} key={idx}
className={{ className={{
[classes.extraPaddingRight]: extraPaddingRight, [classes.extraPaddingRight]: extraPaddingRight,
[classes.extraPadding]: extraPadding, [classes.extraPadding]: extraPadding,
[classes.withSuffix]: suffix, [classes.withSuffix]: suffix,
[classes.withPrefix]: prefix [classes.withPrefix]: prefix
}} }}
width={width} width={width}
size={size} size={size}
bold={bold} bold={bold}
textAlign={textAlign}> textAlign={textAlign}>
{prefix && !isHidden(values) && ( {prefix && !isHidden(values) && (
<PrefixComponent <PrefixComponent
className={classes.prefix} className={classes.prefix}
style={isEditing ? {} : textStyle(values, isEditing)}> style={isEditing ? {} : textStyle(values, isEditing)}>
{typeof prefix === 'function' ? prefix(f) : prefix} {typeof prefix === 'function' ? prefix(f) : prefix}
</PrefixComponent> </PrefixComponent>
)} )}
{isEditing && isField && !isHidden(values) && ( {isEditing && isField && !isHidden(values) && (
<Field name={f} component={input} {...innerProps} /> <Field name={f} component={input} {...innerProps} />
)} )}
{isEditing && !isField && !isHidden(values) && ( {isEditing && !isField && !isHidden(values) && (
<config.input name={f} /> <config.input name={f} />
)} )}
{!isEditing && values && !isHidden(values) && ( {!isEditing && values && !isHidden(values) && (
<div style={textStyle(values, isEditing)}> <div style={textStyle(values, isEditing)}>
{view(values[f], values)} {view(values[f], values)}
</div> </div>
)} )}
{suffix && !isHidden(values) && ( {suffix && !isHidden(values) && (
<SuffixComponent <SuffixComponent
className={classes.suffix} className={classes.suffix}
style={isEditing ? {} : textStyle(values, isEditing)}> style={isEditing ? {} : textStyle(values, isEditing)}>
{suffix} {suffix}
</SuffixComponent> </SuffixComponent>
)} )}
{isHidden(values) && <StripesSvg />} {isHidden(values) && <StripesSvg />}
</Td> </Td>
))} ))}
</div> </div>
) )
} }
const groupStriped = elements => { const groupStriped = elements => {
const [toStripe, noStripe] = R.partition(R.propEq('stripe', true))(elements) const [toStripe, noStripe] = R.partition(R.propEq('stripe', true))(elements)
if (!toStripe.length) { if (!toStripe.length) {
return elements return elements
} }
const index = R.indexOf(toStripe[0], elements) const index = R.indexOf(toStripe[0], elements)
const width = R.compose(R.sum, R.map(R.path(['width'])))(toStripe) const width = R.compose(R.sum, R.map(R.path(['width'])))(toStripe)
return R.insert( return R.insert(
index, index,
{ width, editable: false, view: () => <StripesSvg /> }, { width, editable: false, view: () => <StripesSvg /> },
noStripe noStripe
) )
} }
const ERow = ({ editing, disabled, lastOfGroup, newRow }) => { const ERow = ({ editing, disabled, lastOfGroup, newRow }) => {
const { touched, errors, values } = useFormikContext() const { touched, errors, values } = useFormikContext()
const { const {
elements, elements,
enableEdit, enableEdit,
enableDelete, enableDelete,
error, error,
enableToggle, enableToggle,
rowSize, rowSize,
stripeWhen stripeWhen
} = useContext(TableCtx) } = useContext(TableCtx)
const classes = useStyles() const classes = useStyles()
const shouldStripe = !editing && stripeWhen && stripeWhen(values) const shouldStripe = !editing && stripeWhen && stripeWhen(values)
const innerElements = shouldStripe ? groupStriped(elements) : elements const innerElements = shouldStripe ? groupStriped(elements) : elements
const [toSHeader] = R.partition(R.has('doubleHeader'))(elements) const [toSHeader] = R.partition(R.has('doubleHeader'))(elements)
const extraPaddingIndex = toSHeader?.length const extraPaddingIndex = toSHeader?.length
? R.indexOf(toSHeader[0], elements) ? R.indexOf(toSHeader[0], elements)
: -1 : -1
const extraPaddingRightIndex = toSHeader?.length const extraPaddingRightIndex = toSHeader?.length
? R.indexOf(toSHeader[toSHeader.length - 1], elements) ? R.indexOf(toSHeader[toSHeader.length - 1], elements)
: -1 : -1
const elementToFocusIndex = innerElements.findIndex( const elementToFocusIndex = innerElements.findIndex(
it => it.editable === undefined || it.editable it => it.editable === undefined || it.editable
) )
const classNames = { const classNames = {
[classes.lastOfGroup]: lastOfGroup [classes.lastOfGroup]: lastOfGroup
} }
const touchedErrors = R.pick(R.keys(touched), errors) const touchedErrors = R.pick(R.keys(touched), errors)
const hasTouchedErrors = touchedErrors && R.keys(touchedErrors).length > 0 const hasTouchedErrors = touchedErrors && R.keys(touchedErrors).length > 0
const hasErrors = hasTouchedErrors || !!error const hasErrors = hasTouchedErrors || !!error
const errorMessage = const errorMessage =
error || (touchedErrors && R.values(touchedErrors).join(', ')) error || (touchedErrors && R.values(touchedErrors).join(', '))
return ( return (
<Tr <Tr
className={classnames(classNames)} className={classnames(classNames)}
size={rowSize} size={rowSize}
error={editing && hasErrors} error={editing && hasErrors}
newRow={newRow && !hasErrors} newRow={newRow && !hasErrors}
shouldShowError shouldShowError
errorMessage={errorMessage}> errorMessage={errorMessage}>
{innerElements.map((it, idx) => { {innerElements.map((it, idx) => {
return ( return (
<ECol <ECol
key={idx} key={idx}
config={it} config={it}
editing={editing} editing={editing}
focus={idx === elementToFocusIndex && editing} focus={idx === elementToFocusIndex && editing}
extraPaddingRight={extraPaddingRightIndex === idx} extraPaddingRight={extraPaddingRightIndex === idx}
extraPadding={extraPaddingIndex === idx} extraPadding={extraPaddingIndex === idx}
/> />
) )
})} })}
{(enableEdit || enableDelete || enableToggle) && ( {(enableEdit || enableDelete || enableToggle) && (
<ActionCol disabled={disabled} editing={editing} /> <ActionCol disabled={disabled} editing={editing} />
)} )}
</Tr> </Tr>
) )
} }
export default ERow export default ERow

View file

@ -75,7 +75,7 @@ const ETable = ({
setSaving(true) setSaving(true)
const it = validationSchema.cast(value, { assert: 'ignore-optionality'}) const it = validationSchema.cast(value, { assert: 'ignore-optionality' })
const index = R.findIndex(R.propEq('id', it.id))(data) const index = R.findIndex(R.propEq('id', it.id))(data)
const list = index !== -1 ? R.update(index, it, data) : R.prepend(it, data) const list = index !== -1 ? R.update(index, it, data) : R.prepend(it, data)

View file

@ -6,7 +6,8 @@ import { styles } from './TextInput.styles'
const useStyles = makeStyles(styles) const useStyles = makeStyles(styles)
const mask = /(\+)(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)(\d{1,3}){0,1}(\d{1,3}){0,1}(\d{1,3}){0,1}(\d{1,3}){0,1}(\d{1,2}){0,1}$/ const mask =
/(\+)(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)(\d{1,3}){0,1}(\d{1,3}){0,1}(\d{1,3}){0,1}(\d{1,3}){0,1}(\d{1,2}){0,1}$/
const maskValue = value => const maskValue = value =>
value ? value.replace(mask, '$1 $2 $3 $4 $5 $6') : '' value ? value.replace(mask, '$1 $2 $3 $4 $5 $6') : ''

View file

@ -1,24 +1,24 @@
import React, { memo } from 'react' import React, { memo } from 'react'
import { SecretInput } from '../base' import { SecretInput } from '../base'
const SecretInputFormik = memo(({ isPasswordFilled, ...props }) => { const SecretInputFormik = memo(({ isPasswordFilled, ...props }) => {
const { name, onChange, onBlur, value } = props.field const { name, onChange, onBlur, value } = props.field
const { touched, errors } = props.form const { touched, errors } = props.form
const error = !isPasswordFilled && !!(touched[name] && errors[name]) const error = !isPasswordFilled && !!(touched[name] && errors[name])
return ( return (
<SecretInput <SecretInput
name={name} name={name}
isPasswordFilled={isPasswordFilled} isPasswordFilled={isPasswordFilled}
onChange={onChange} onChange={onChange}
onBlur={onBlur} onBlur={onBlur}
value={value} value={value}
error={error} error={error}
{...props} {...props}
/> />
) )
}) })
export default SecretInputFormik export default SecretInputFormik

View file

@ -141,7 +141,9 @@ const Header = memo(({ tree, user }) => {
className={classnames(classes.link, classes.whiteLink)} className={classnames(classes.link, classes.whiteLink)}
activeClassName={classes.activeLink}> activeClassName={classes.activeLink}>
<li className={classes.li}> <li className={classes.li}>
<span className={classes.forceSize} data-forcesize={it.label}> <span
className={classes.forceSize}
data-forcesize={it.label}>
{it.label} {it.label}
</span> </span>
</li> </li>

View file

@ -26,7 +26,10 @@ const Sidebar = ({
{loading && <P>Loading...</P>} {loading && <P>Loading...</P>}
{!loading && {!loading &&
data?.map((it, idx) => ( data?.map((it, idx) => (
<div key={idx} className={classes.linkWrapper} onClick={() => onClick(it)}> <div
key={idx}
className={classes.linkWrapper}
onClick={() => onClick(it)}>
<div <div
key={idx} key={idx}
className={classnames({ className={classnames({

View file

@ -50,7 +50,6 @@ const Graph = ({
const step = R.clone(start) const step = R.clone(start)
while (step <= end) { while (step <= end) {
ticks.push(R.clone(step)) ticks.push(R.clone(step))
step.setUTCHours(step.getUTCHours() + interval) step.setUTCHours(step.getUTCHours() + interval)
@ -105,19 +104,25 @@ const Graph = ({
]) ])
.rangeRound([GRAPH_MARGIN.left, GRAPH_WIDTH - GRAPH_MARGIN.right]) .rangeRound([GRAPH_MARGIN.left, GRAPH_WIDTH - GRAPH_MARGIN.right])
const groupedByDateInterval = R.map(it => { const groupedByDateInterval = R.map(
const lowerBound = R.clone(it) it => {
it.setUTCHours(it.getUTCHours() + 2) const lowerBound = R.clone(it)
const upperBound = R.clone(it) it.setUTCHours(it.getUTCHours() + 2)
return [lowerBound, filterByHourInterval(lowerBound, upperBound)] const upperBound = R.clone(it)
}, R.init(getTickIntervals(x.domain(), 2))) return [lowerBound, filterByHourInterval(lowerBound, upperBound)]
},
R.init(getTickIntervals(x.domain(), 2))
)
const groupedByTxClass = R.map(it => { const groupedByTxClass = R.map(
const lowerBound = R.clone(it) it => {
it.setUTCHours(it.getUTCHours() + 2) const lowerBound = R.clone(it)
const upperBound = R.clone(it) it.setUTCHours(it.getUTCHours() + 2)
return [lowerBound, txClassByHourInterval(lowerBound, upperBound)] const upperBound = R.clone(it)
}, R.init(getTickIntervals(x.domain(), 2))) return [lowerBound, txClassByHourInterval(lowerBound, upperBound)]
},
R.init(getTickIntervals(x.domain(), 2))
)
const y = d3 const y = d3
.scaleLinear() .scaleLinear()
@ -416,9 +421,7 @@ const Graph = ({
]) ])
useEffect(() => { useEffect(() => {
d3.select(ref.current) d3.select(ref.current).selectAll('*').remove()
.selectAll('*')
.remove()
drawChart() drawChart()
}, [drawChart]) }, [drawChart])

View file

@ -404,10 +404,7 @@ const Graph = ({
) )
// Left side breakpoint label // Left side breakpoint label
.call(g => { .call(g => {
const separator = d3 const separator = d3?.select('.dateSeparator')?.node()?.getBBox()
?.select('.dateSeparator')
?.node()
?.getBBox()
if (!separator) return if (!separator) return
@ -428,10 +425,7 @@ const Graph = ({
}) })
// Right side breakpoint label // Right side breakpoint label
.call(g => { .call(g => {
const separator = d3 const separator = d3?.select('.dateSeparator')?.node()?.getBBox()
?.select('.dateSeparator')
?.node()
?.getBBox()
if (!separator) return if (!separator) return
@ -560,9 +554,7 @@ const Graph = ({
]) ])
useEffect(() => { useEffect(() => {
d3.select(ref.current) d3.select(ref.current).selectAll('*').remove()
.selectAll('*')
.remove()
drawChart() drawChart()
}, [drawChart]) }, [drawChart])

View file

@ -455,10 +455,7 @@ const Graph = ({
) )
// Left side breakpoint label // Left side breakpoint label
.call(g => { .call(g => {
const separator = d3 const separator = d3?.select('.dateSeparator')?.node()?.getBBox()
?.select('.dateSeparator')
?.node()
?.getBBox()
if (!separator) return if (!separator) return
@ -479,10 +476,7 @@ const Graph = ({
}) })
// Right side breakpoint label // Right side breakpoint label
.call(g => { .call(g => {
const separator = d3 const separator = d3?.select('.dateSeparator')?.node()?.getBBox()
?.select('.dateSeparator')
?.node()
?.getBBox()
if (!separator) return if (!separator) return
@ -636,9 +630,7 @@ const Graph = ({
]) ])
useEffect(() => { useEffect(() => {
d3.select(ref.current) d3.select(ref.current).selectAll('*').remove()
.selectAll('*')
.remove()
drawChart() drawChart()
}, [drawChart]) }, [drawChart])

View file

@ -36,10 +36,13 @@ const Graph = ({ data, machines, currency, selectedMachine }) => {
const filledMachines = const filledMachines =
R.length(machines) >= AMOUNT_OF_MACHINES R.length(machines) >= AMOUNT_OF_MACHINES
? machinesClone ? machinesClone
: R.map(it => { : R.map(
if (!R.isNil(machinesClone[it])) return machinesClone[it] it => {
return { code: `ghostMachine${it}`, display: `` } if (!R.isNil(machinesClone[it])) return machinesClone[it]
}, R.times(R.identity, AMOUNT_OF_MACHINES)) return { code: `ghostMachine${it}`, display: `` }
},
R.times(R.identity, AMOUNT_OF_MACHINES)
)
const txByDevice = R.reduce( const txByDevice = R.reduce(
(acc, value) => { (acc, value) => {
@ -108,8 +111,9 @@ const Graph = ({ data, machines, currency, selectedMachine }) => {
.axisBottom(x) .axisBottom(x)
.tickFormat( .tickFormat(
d => d =>
`${R.find(it => it.code === d[0], filledMachines).display ?? `${
''}` R.find(it => it.code === d[0], filledMachines).display ?? ''
}`
) )
.tickSize(0) .tickSize(0)
.tickPadding(10) .tickPadding(10)
@ -140,14 +144,14 @@ const Graph = ({ data, machines, currency, selectedMachine }) => {
) )
const positionXAxisLabels = useCallback(() => { const positionXAxisLabels = useCallback(() => {
d3.selectAll('.x-axis-1 .tick text').attr('transform', function(d) { d3.selectAll('.x-axis-1 .tick text').attr('transform', function (d) {
const widthPerEntry = (x.range()[1] - x.range()[0]) / AMOUNT_OF_MACHINES const widthPerEntry = (x.range()[1] - x.range()[0]) / AMOUNT_OF_MACHINES
return `translate(${-widthPerEntry / 2.25 + this.getBBox().width / 2}, 0)` return `translate(${-widthPerEntry / 2.25 + this.getBBox().width / 2}, 0)`
}) })
}, [x]) }, [x])
const positionXAxis2Labels = useCallback(() => { const positionXAxis2Labels = useCallback(() => {
d3.selectAll('.x-axis-2 .tick text').attr('transform', function(d) { d3.selectAll('.x-axis-2 .tick text').attr('transform', function (d) {
const widthPerEntry = (x.range()[1] - x.range()[0]) / AMOUNT_OF_MACHINES const widthPerEntry = (x.range()[1] - x.range()[0]) / AMOUNT_OF_MACHINES
return `translate(${widthPerEntry / 2.25 - this.getBBox().width / 2}, 0)` return `translate(${widthPerEntry / 2.25 - this.getBBox().width / 2}, 0)`
}) })
@ -295,9 +299,7 @@ const Graph = ({ data, machines, currency, selectedMachine }) => {
]) ])
useEffect(() => { useEffect(() => {
d3.select(ref.current) d3.select(ref.current).selectAll('*').remove()
.selectAll('*')
.remove()
drawChart() drawChart()
}, [drawChart]) }, [drawChart])

View file

@ -48,10 +48,7 @@ const GET_USER_DATA = gql`
` `
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
email: Yup.string() email: Yup.string().label('Email').required().email(),
.label('Email')
.required()
.email(),
password: Yup.string().required('Password field is required'), password: Yup.string().required('Password field is required'),
rememberMe: Yup.boolean() rememberMe: Yup.boolean()
}) })

View file

@ -109,9 +109,7 @@ const BlacklistAdvanced = ({
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
label: Yup.string().required('A label is required!'), label: Yup.string().required('A label is required!'),
content: Yup.string() content: Yup.string().required('The message content is required!').trim()
.required('The message content is required!')
.trim()
}) })
return ( return (

View file

@ -36,9 +36,7 @@ const BlackListModal = ({ onClose, addToBlacklist, errorMsg }) => {
address: '' address: ''
}} }}
validationSchema={Yup.object({ validationSchema={Yup.object({
address: Yup.string() address: Yup.string().trim().required('An address is required')
.trim()
.required('An address is required')
})} })}
onSubmit={({ address }) => { onSubmit={({ address }) => {
handleAddToBlacklist(address.trim()) handleAddToBlacklist(address.trim())

View file

@ -83,63 +83,43 @@ const Wizard = ({ machine, locale, onClose, save, error }) => {
cassette1: cassette1:
machine.numberOfCassettes >= 1 && step >= 1 machine.numberOfCassettes >= 1 && step >= 1
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
cassette2: cassette2:
machine.numberOfCassettes >= 2 && step >= 2 machine.numberOfCassettes >= 2 && step >= 2
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
cassette3: cassette3:
machine.numberOfCassettes >= 3 && step >= 3 machine.numberOfCassettes >= 3 && step >= 3
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
cassette4: cassette4:
machine.numberOfCassettes >= 4 && step >= 4 machine.numberOfCassettes >= 4 && step >= 4
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
recycler1: recycler1:
machine.numberOfRecyclers >= 1 && step >= machine.numberOfCassettes + 1 machine.numberOfRecyclers >= 1 && step >= machine.numberOfCassettes + 1
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
recycler2: recycler2:
machine.numberOfRecyclers >= 2 && step >= machine.numberOfCassettes + 2 machine.numberOfRecyclers >= 2 && step >= machine.numberOfCassettes + 2
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
recycler3: recycler3:
machine.numberOfRecyclers >= 3 && step >= machine.numberOfCassettes + 3 machine.numberOfRecyclers >= 3 && step >= machine.numberOfCassettes + 3
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
recycler4: recycler4:
machine.numberOfRecyclers >= 4 && step >= machine.numberOfCassettes + 4 machine.numberOfRecyclers >= 4 && step >= machine.numberOfCassettes + 4
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
recycler5: recycler5:
machine.numberOfRecyclers >= 5 && step >= machine.numberOfCassettes + 5 machine.numberOfRecyclers >= 5 && step >= machine.numberOfCassettes + 5
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber)
.nullable(),
recycler6: recycler6:
machine.numberOfRecyclers >= 6 && step >= machine.numberOfCassettes + 6 machine.numberOfRecyclers >= 6 && step >= machine.numberOfCassettes + 6
? Yup.number().required() ? Yup.number().required()
: Yup.number() : Yup.number().transform(transformNumber).nullable()
.transform(transformNumber)
.nullable()
}) })
return ( return (

View file

@ -325,10 +325,7 @@ const getOverridesSchema = (values, rawData, locale) => {
const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills) const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills)
return Yup.object().shape({ return Yup.object().shape({
machine: Yup.string() machine: Yup.string().nullable().label('Machine').required(),
.nullable()
.label('Machine')
.required(),
cryptoCurrencies: Yup.array() cryptoCurrencies: Yup.array()
.test({ .test({
test() { test() {
@ -474,13 +471,8 @@ const getListCommissionsSchema = locale => {
const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills) const highestBill = R.isEmpty(bills) ? CURRENCY_MAX : Math.max(...bills)
return Yup.object().shape({ return Yup.object().shape({
machine: Yup.string() machine: Yup.string().label('Machine').required(),
.label('Machine') cryptoCurrencies: Yup.array().label('Crypto currency').required().min(1),
.required(),
cryptoCurrencies: Yup.array()
.label('Crypto currency')
.required()
.min(1),
cashIn: Yup.number() cashIn: Yup.number()
.label('Cash-in') .label('Cash-in')
.min(percentMin) .min(percentMin)

View file

@ -89,8 +89,8 @@ const CustomerData = ({
const sanctionsDisplay = !sanctionsAt const sanctionsDisplay = !sanctionsAt
? 'Not checked yet' ? 'Not checked yet'
: sanctions : sanctions
? 'Passed' ? 'Passed'
: 'Failed' : 'Failed'
const sortByName = R.sortBy( const sortByName = R.sortBy(
R.compose(R.toLower, R.path(['customInfoRequest', 'customRequest', 'name'])) R.compose(R.toLower, R.path(['customInfoRequest', 'customRequest', 'name']))
@ -263,9 +263,7 @@ const CustomerData = ({
src={ src={
!R.isNil(previewPhoto) !R.isNil(previewPhoto)
? URL.createObjectURL(previewPhoto) ? URL.createObjectURL(previewPhoto)
: `/front-camera-photo/${R.path(['frontCameraPath'])( : `/front-camera-photo/${R.path(['frontCameraPath'])(customer)}`
customer
)}`
} }
/> />
) : null ) : null
@ -304,9 +302,7 @@ const CustomerData = ({
src={ src={
!R.isNil(previewCard) !R.isNil(previewCard)
? URL.createObjectURL(previewCard) ? URL.createObjectURL(previewCard)
: `/id-card-photo/${R.path(['idCardPhotoPath'])( : `/id-card-photo/${R.path(['idCardPhotoPath'])(customer)}`
customer
)}`
} }
/> />
) : null ) : null
@ -386,44 +382,50 @@ const CustomerData = ({
}) })
}, customInfoRequests) }, customInfoRequests)
R.forEach(it => { R.forEach(
customFields.push({ it => {
fields: [ customFields.push({
{ fields: [
name: it.label, {
label: it.label, name: it.label,
value: it.value ?? '', label: it.label,
component: TextInput, value: it.value ?? '',
editable: true component: TextInput,
editable: true
}
],
title: it.label,
titleIcon: <EditIcon className={classes.editIcon} />,
save: values => {
updateCustomEntry({
fieldId: it.id,
value: values[it.label]
})
},
deleteEditedData: () => {},
validationSchema: Yup.object().shape({
[it.label]: Yup.string()
}),
initialValues: {
[it.label]: it.value ?? ''
} }
], })
title: it.label, },
titleIcon: <EditIcon className={classes.editIcon} />, R.path(['customFields'])(customer) ?? []
save: values => { )
updateCustomEntry({
fieldId: it.id,
value: values[it.label]
})
},
deleteEditedData: () => {},
validationSchema: Yup.object().shape({
[it.label]: Yup.string()
}),
initialValues: {
[it.label]: it.value ?? ''
}
})
}, R.path(['customFields'])(customer) ?? [])
R.forEach(it => { R.forEach(
initialValues.smsData[it] = smsData[it] it => {
smsDataElements.push({ initialValues.smsData[it] = smsData[it]
name: it, smsDataElements.push({
label: onlyFirstToUpper(it), name: it,
component: TextInput, label: onlyFirstToUpper(it),
editable: false component: TextInput,
}) editable: false
}, R.keys(smsData) ?? []) })
},
R.keys(smsData) ?? []
)
const externalCompliance = R.map(it => ({ const externalCompliance = R.map(it => ({
fields: [ fields: [
@ -492,7 +494,9 @@ const CustomerData = ({
deleteEditedData={deleteEditedData} deleteEditedData={deleteEditedData}
retrieveAdditionalData={retrieveAdditionalData} retrieveAdditionalData={retrieveAdditionalData}
checkAgainstSanctions={checkAgainstSanctions} checkAgainstSanctions={checkAgainstSanctions}
editable={editable}>{children}</EditableCard> editable={editable}>
{children}
</EditableCard>
) )
} }
@ -509,7 +513,9 @@ const CustomerData = ({
titleIcon={titleIcon} titleIcon={titleIcon}
editable={false} editable={false}
hasImage={hasImage} hasImage={hasImage}
fields={fields}>{children}</EditableCard> fields={fields}>
{children}
</EditableCard>
) )
} }
@ -519,24 +525,25 @@ const CustomerData = ({
<div> <div>
<div className={classes.header}> <div className={classes.header}>
<H3 className={classes.title}>{'Customer data'}</H3> <H3 className={classes.title}>{'Customer data'}</H3>
{// TODO: Remove false condition for next release {
// false && ( // TODO: Remove false condition for next release
// <> // false && (
// <FeatureButton // <>
// active={!listView} // <FeatureButton
// className={classes.viewIcons} // active={!listView}
// Icon={OverviewIcon} // className={classes.viewIcons}
// InverseIcon={OverviewReversedIcon} // Icon={OverviewIcon}
// onClick={() => setListView(false)} // InverseIcon={OverviewReversedIcon}
// /> // onClick={() => setListView(false)}
// <FeatureButton // />
// active={listView} // <FeatureButton
// className={classes.viewIcons} // active={listView}
// Icon={CustomerListViewIcon} // className={classes.viewIcons}
// InverseIcon={CustomerListViewReversedIcon} // Icon={CustomerListViewIcon}
// onClick={() => setListView(true)}></FeatureButton> // InverseIcon={CustomerListViewReversedIcon}
// </> // onClick={() => setListView(true)}></FeatureButton>
// ) // </>
// )
} }
</div> </div>
<div> <div>

View file

@ -59,15 +59,14 @@ const CustomerNotes = ({
<div className={classes.notesChipList}> <div className={classes.notesChipList}>
<NewNoteCard setOpenModal={setOpenModal} /> <NewNoteCard setOpenModal={setOpenModal} />
{customerNotes.map((it, idx) => ( {customerNotes.map((it, idx) => (
<NoteCard <NoteCard
key={idx} key={idx}
note={it} note={it}
deleteNote={deleteNote} deleteNote={deleteNote}
handleClick={setEditing} handleClick={setEditing}
timezone={timezone} timezone={timezone}
/> />
) ))}
)}
</div> </div>
)} )}
{!R.isNil(editing) && ( {!R.isNil(editing) && (

View file

@ -552,8 +552,8 @@ const CustomerProfile = memo(() => {
{name.length {name.length
? name ? name
: email?.length : email?.length
? email ? email
: getFormattedPhone(phone, locale.country)} : getFormattedPhone(phone, locale.country)}
</Label2> </Label2>
</Breadcrumbs> </Breadcrumbs>
<div className={classes.panels}> <div className={classes.panels}>

View file

@ -120,9 +120,8 @@ const Customers = () => {
onCompleted: data => setFilteredCustomers(R.path(['customers'])(data)) onCompleted: data => setFilteredCustomers(R.path(['customers'])(data))
}) })
const { data: filtersResponse, loading: loadingFilters } = useQuery( const { data: filtersResponse, loading: loadingFilters } =
GET_CUSTOMER_FILTERS useQuery(GET_CUSTOMER_FILTERS)
)
const [createNewCustomer] = useMutation(CREATE_CUSTOMER, { const [createNewCustomer] = useMutation(CREATE_CUSTOMER, {
onCompleted: () => setShowCreationModal(false), onCompleted: () => setShowCreationModal(false),

View file

@ -134,8 +134,8 @@ const CustomInfoRequestsData = ({ data }) => {
it.approved === null it.approved === null
? { label: 'Pending', type: 'neutral' } ? { label: 'Pending', type: 'neutral' }
: it.approved === false : it.approved === false
? { label: 'Rejected', type: 'error' } ? { label: 'Rejected', type: 'error' }
: { label: 'Accepted', type: 'success' } : { label: 'Accepted', type: 'success' }
return ( return (
<> <>

View file

@ -59,8 +59,8 @@ const CustomerDetails = memo(({ customer, photosData, locale, timezone }) => {
{name.length {name.length
? name ? name
: email?.length : email?.length
? email ? email
: getFormattedPhone(phone, locale.country)} : getFormattedPhone(phone, locale.country)}
</H2> </H2>
</div> </div>
<Box display="flex" mt="auto"> <Box display="flex" mt="auto">

View file

@ -170,8 +170,8 @@ const EditableCard = ({
state === OVERRIDE_PENDING state === OVERRIDE_PENDING
? { label: 'Pending', type: 'neutral' } ? { label: 'Pending', type: 'neutral' }
: state === OVERRIDE_REJECTED : state === OVERRIDE_REJECTED
? { label: 'Rejected', type: 'error' } ? { label: 'Rejected', type: 'error' }
: { label: 'Accepted', type: 'success' } : { label: 'Accepted', type: 'success' }
return ( return (
<div> <div>

View file

@ -38,9 +38,7 @@ const IdCardPhotoCard = memo(({ customerData, updateCustomer }) => {
{customerData.idCardPhotoPath ? ( {customerData.idCardPhotoPath ? (
<img <img
className={classes.idCardPhoto} className={classes.idCardPhoto}
src={`/id-card-photo/${R.path(['idCardPhotoPath'])( src={`/id-card-photo/${R.path(['idCardPhotoPath'])(customerData)}`}
customerData
)}`}
alt="" alt=""
/> />
) : ( ) : (

View file

@ -18,10 +18,7 @@ const initialValues = {
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
title: Yup.string() title: Yup.string().required().trim().max(25),
.required()
.trim()
.max(25),
content: Yup.string().required() content: Yup.string().required()
}) })

View file

@ -93,8 +93,8 @@ const PropertyCard = memo(
state === OVERRIDE_PENDING state === OVERRIDE_PENDING
? { label: 'Pending', type: 'neutral' } ? { label: 'Pending', type: 'neutral' }
: state === OVERRIDE_REJECTED : state === OVERRIDE_REJECTED
? { label: 'Rejected', type: 'error' } ? { label: 'Rejected', type: 'error' }
: { label: 'Accepted', type: 'success' } : { label: 'Accepted', type: 'success' }
return ( return (
<Paper <Paper

View file

@ -143,9 +143,9 @@ const getFormattedPhone = (phone, country) => {
const getName = it => { const getName = it => {
const idData = R.path(['idCardData'])(it) const idData = R.path(['idCardData'])(it)
return `${R.path(['firstName'])(idData) ?? ''} ${R.path(['lastName'])( return `${R.path(['firstName'])(idData) ?? ''} ${
idData R.path(['lastName'])(idData) ?? ''
) ?? ''}`.trim() }`.trim()
} }
// Manual Entry Wizard // Manual Entry Wizard

View file

@ -126,10 +126,10 @@ const RefLineChart = ({
]) ])
.enter() .enter()
.append('stop') .append('stop')
.attr('offset', function(d) { .attr('offset', function (d) {
return d.offset return d.offset
}) })
.attr('stop-color', function(d) { .attr('stop-color', function (d) {
return d.color return d.color
}) })
@ -152,20 +152,20 @@ const RefLineChart = ({
const line = d3 const line = d3
.line() .line()
.x(function(d) { .x(function (d) {
return x(new Date(d.created)) return x(new Date(d.created))
}) })
.y(function(d) { .y(function (d) {
return y(d.profit) return y(d.profit)
}) })
const area = d3 const area = d3
.area() .area()
.x(function(d) { .x(function (d) {
return x(new Date(d.created)) return x(new Date(d.created))
}) })
.y0(height) .y0(height)
.y1(function(d) { .y1(function (d) {
return y(d.profit) return y(d.profit)
}) })
@ -186,9 +186,7 @@ const RefLineChart = ({
useEffect(() => { useEffect(() => {
// first we clear old chart DOM elements on component update // first we clear old chart DOM elements on component update
d3.select(svgRef.current) d3.select(svgRef.current).selectAll('*').remove()
.selectAll('*')
.remove()
drawGraph() drawGraph()
}, [drawGraph, realData]) }, [drawGraph, realData])

View file

@ -211,12 +211,7 @@ const Graph = ({ data, timeFrame, timezone }) => {
g g
.append('g') .append('g')
.selectAll('line') .selectAll('line')
.data( .data(d3.axisLeft(y).scale().ticks(5))
d3
.axisLeft(y)
.scale()
.ticks(5)
)
.join('line') .join('line')
.attr('y1', d => 0.5 + y(d)) .attr('y1', d => 0.5 + y(d))
.attr('y2', d => 0.5 + y(d)) .attr('y2', d => 0.5 + y(d))
@ -240,10 +235,7 @@ const Graph = ({ data, timeFrame, timezone }) => {
) )
// Left side breakpoint label // Left side breakpoint label
.call(g => { .call(g => {
const separator = d3 const separator = d3?.select('.dateSeparator')?.node()?.getBBox()
?.select('.dateSeparator')
?.node()
?.getBBox()
if (!separator) return if (!separator) return
@ -261,10 +253,7 @@ const Graph = ({ data, timeFrame, timezone }) => {
}) })
// Right side breakpoint label // Right side breakpoint label
.call(g => { .call(g => {
const separator = d3 const separator = d3?.select('.dateSeparator')?.node()?.getBBox()
?.select('.dateSeparator')
?.node()
?.getBBox()
if (!separator) return if (!separator) return
@ -355,9 +344,7 @@ const Graph = ({ data, timeFrame, timezone }) => {
]) ])
useEffect(() => { useEffect(() => {
d3.select(ref.current) d3.select(ref.current).selectAll('*').remove()
.selectAll('*')
.remove()
drawChart() drawChart()
}, [drawChart]) }, [drawChart])

View file

@ -108,14 +108,13 @@ const MachinesTable = ({ machines = [], numToRender }) => {
</div> </div>
</HeaderCell> */} </HeaderCell> */}
{R.times(R.identity, maxNumberOfCassettes).map((it, idx) => ( {R.times(R.identity, maxNumberOfCassettes).map((it, idx) => (
<HeaderCell key={idx}> <HeaderCell key={idx}>
<div className={classes.header}> <div className={classes.header}>
<TxOutIcon /> <TxOutIcon />
<Label2 className={classes.label}> {it + 1}</Label2> <Label2 className={classes.label}> {it + 1}</Label2>
</div> </div>
</HeaderCell> </HeaderCell>
) ))}
)}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -142,18 +141,18 @@ const MachinesTable = ({ machines = [], numToRender }) => {
<Status status={machine.statuses[0]} /> <Status status={machine.statuses[0]} />
</StyledCell> </StyledCell>
{R.range(1, maxNumberOfCassettes + 1).map((it, idx) => {R.range(1, maxNumberOfCassettes + 1).map((it, idx) =>
machine.numberOfCassettes >= it ? ( machine.numberOfCassettes >= it ? (
<StyledCell key={idx} align="left"> <StyledCell key={idx} align="left">
{makePercentageText( {makePercentageText(
it, it,
machine.cashUnits[`cassette${it}`] machine.cashUnits[`cassette${it}`]
)} )}
</StyledCell> </StyledCell>
) : ( ) : (
<StyledCell key={idx} align="left"> <StyledCell key={idx} align="left">
<TL2>{`— %`}</TL2> <TL2>{`— %`}</TL2>
</StyledCell> </StyledCell>
) )
)} )}
</TableRow> </TableRow>
) )

View file

@ -153,42 +153,18 @@ const overrides = (auxData, auxElements, configureCoin) => {
} }
const LocaleSchema = Yup.object().shape({ const LocaleSchema = Yup.object().shape({
country: Yup.string() country: Yup.string().label('Country').required(),
.label('Country') fiatCurrency: Yup.string().label('Fiat currency').required(),
.required(), languages: Yup.array().label('Languages').required().min(1).max(4),
fiatCurrency: Yup.string() cryptoCurrencies: Yup.array().label('Crypto currencies').required().min(1),
.label('Fiat currency') timezone: Yup.string().label('Timezone').required()
.required(),
languages: Yup.array()
.label('Languages')
.required()
.min(1)
.max(4),
cryptoCurrencies: Yup.array()
.label('Crypto currencies')
.required()
.min(1),
timezone: Yup.string()
.label('Timezone')
.required()
}) })
const OverridesSchema = Yup.object().shape({ const OverridesSchema = Yup.object().shape({
machine: Yup.string() machine: Yup.string().label('Machine').required(),
.label('Machine') country: Yup.string().label('Country').required(),
.required(), languages: Yup.array().label('Languages').required().min(1).max(4),
country: Yup.string() cryptoCurrencies: Yup.array().label('Crypto currencies').required().min(1)
.label('Country')
.required(),
languages: Yup.array()
.label('Languages')
.required()
.min(1)
.max(4),
cryptoCurrencies: Yup.array()
.label('Crypto currencies')
.required()
.min(1)
}) })
const localeDefaults = { const localeDefaults = {

View file

@ -65,9 +65,8 @@ const IndividualDiscounts = () => {
const { data: discountResponse, loading: discountLoading } = useQuery( const { data: discountResponse, loading: discountLoading } = useQuery(
GET_INDIVIDUAL_DISCOUNTS GET_INDIVIDUAL_DISCOUNTS
) )
const { data: customerData, loading: customerLoading } = useQuery( const { data: customerData, loading: customerLoading } =
GET_CUSTOMERS useQuery(GET_CUSTOMERS)
)
const [createDiscount, { error: creationError }] = useMutation( const [createDiscount, { error: creationError }] = useMutation(
CREATE_DISCOUNT, CREATE_DISCOUNT,

View file

@ -21,14 +21,8 @@ const initialValues = {
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
code: Yup.string() code: Yup.string().required().trim().max(25),
.required() discount: Yup.number().required().min(0).max(100)
.trim()
.max(25),
discount: Yup.number()
.required()
.min(0)
.max(100)
}) })
const PromoCodesModal = ({ showModal, onClose, errorMsg, addCode }) => { const PromoCodesModal = ({ showModal, onClose, errorMsg, addCode }) => {

View file

@ -81,9 +81,8 @@ const Logs = () => {
const deviceId = selected?.deviceId const deviceId = selected?.deviceId
const { data: machineResponse, loading: machinesLoading } = useQuery( const { data: machineResponse, loading: machinesLoading } =
GET_MACHINES useQuery(GET_MACHINES)
)
const { data: configResponse, loading: configLoading } = useQuery(GET_DATA) const { data: configResponse, loading: configLoading } = useQuery(GET_DATA)
const timezone = R.path(['config', 'locale_timezone'], configResponse) const timezone = R.path(['config', 'locale_timezone'], configResponse)

View file

@ -106,7 +106,7 @@ const CashUnitDetails = ({
<div className={classes.billList}> <div className={classes.billList}>
<Label1>Cash box</Label1> <Label1>Cash box</Label1>
{R.isEmpty(billCount) && <TL2 noMargin>Empty</TL2>} {R.isEmpty(billCount) && <TL2 noMargin>Empty</TL2>}
{(R.keys(billCount)).map((it, idx) => ( {R.keys(billCount).map((it, idx) => (
<span key={idx}> <span key={idx}>
<TL2 noMargin>{billCount[it]}</TL2> <TL2 noMargin>{billCount[it]}</TL2>
<Chip label={`${it} ${currency}`} /> <Chip label={`${it} ${currency}`} />
@ -149,7 +149,7 @@ const CashUnitDetails = ({
noMargin noMargin
className={classes.label}>{`Loading boxes`}</Label1> className={classes.label}>{`Loading boxes`}</Label1>
<div className={classes.loadingBoxes}> <div className={classes.loadingBoxes}>
{(R.range(1, machine.numberOfCassettes + 1)).map((it, idx) => ( {R.range(1, machine.numberOfCassettes + 1).map((it, idx) => (
<CashOut <CashOut
key={idx} key={idx}
width={60} width={60}

View file

@ -182,10 +182,8 @@ const WizardStep = ({
const numberOfCassettes = machine.numberOfCassettes const numberOfCassettes = machine.numberOfCassettes
const numberOfRecyclers = machine.numberOfRecyclers const numberOfRecyclers = machine.numberOfRecyclers
const { const { name: cashUnitField, category: cashUnitCategory } =
name: cashUnitField, getCashUnitFieldName(step, numberOfCassettes, numberOfRecyclers)
category: cashUnitCategory
} = getCashUnitFieldName(step, numberOfCassettes, numberOfRecyclers)
const originalCashUnitCount = machine?.cashUnits?.[cashUnitField] const originalCashUnitCount = machine?.cashUnits?.[cashUnitField]
const cashUnitDenomination = cashoutSettings?.[cashUnitField] const cashUnitDenomination = cashoutSettings?.[cashUnitField]
@ -317,8 +315,8 @@ const WizardStep = ({
{cashUnitCategory === 'cassette' {cashUnitCategory === 'cassette'
? `dispenser` ? `dispenser`
: cashUnitCategory === 'recycler' : cashUnitCategory === 'recycler'
? `recycler` ? `recycler`
: ``} : ``}
) )
</H4> </H4>
</div> </div>

View file

@ -57,7 +57,7 @@ const getElements = (
return ( return (
<div className={classes.unitsRow}> <div className={classes.unitsRow}>
<div className={classes.units}> <div className={classes.units}>
{(R.range(1, m.numberOfCassettes + 1)).map((it, idx) => ( {R.range(1, m.numberOfCassettes + 1).map((it, idx) => (
<CashOutLite <CashOutLite
key={idx} key={idx}
width={'100%'} width={'100%'}

View file

@ -33,14 +33,8 @@ const SingleFieldEditableNumber = ({
setSaving(false) setSaving(false)
} }
const { const { save, data, currency, isEditing, isDisabled, setEditing } =
save, useContext(NotificationsCtx)
data,
currency,
isEditing,
isDisabled,
setEditing
} = useContext(NotificationsCtx)
const schema = Yup.object().shape({ const schema = Yup.object().shape({
[name]: Yup.number() [name]: Yup.number()

View file

@ -14,14 +14,8 @@ const useStyles = makeStyles(styles)
const CryptoBalanceAlerts = ({ section, fieldWidth }) => { const CryptoBalanceAlerts = ({ section, fieldWidth }) => {
const classes = useStyles() const classes = useStyles()
const { const { data, save, currency, setEditing, isEditing, isDisabled } =
data, useContext(NotificationsCtx)
save,
currency,
setEditing,
isEditing,
isDisabled
} = useContext(NotificationsCtx)
return ( return (
<div className={classes.cryptoBalanceAlerts}> <div className={classes.cryptoBalanceAlerts}>

View file

@ -79,9 +79,7 @@ const FiatBalanceOverrides = ({ config, section }) => {
const percentMax = 100 const percentMax = 100
const validationSchema = Yup.object() const validationSchema = Yup.object()
.shape({ .shape({
[MACHINE_KEY]: Yup.string() [MACHINE_KEY]: Yup.string().label('Machine').required(),
.label('Machine')
.required(),
[CASHBOX_KEY]: Yup.number() [CASHBOX_KEY]: Yup.number()
.label('Cash box') .label('Cash box')
.transform(transformNumber) .transform(transformNumber)

View file

@ -1,84 +1,87 @@
import * as R from 'ramda' import * as R from 'ramda'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import Autocomplete from 'src/components/inputs/formik/Autocomplete' import Autocomplete from 'src/components/inputs/formik/Autocomplete'
import * as Yup from 'yup' import * as Yup from 'yup'
import { Table as EditableTable } from 'src/components/editableTable' import { Table as EditableTable } from 'src/components/editableTable'
import { toNamespace, fromNamespace } from 'src/utils/config' import { toNamespace, fromNamespace } from 'src/utils/config'
import NotificationsCtx from '../NotificationsContext' import NotificationsCtx from '../NotificationsContext'
const filterClass = type => R.filter(it => it.class === type) const filterClass = type => R.filter(it => it.class === type)
const ThirdPartyProvider = () => { const ThirdPartyProvider = () => {
const { save, data: _data, error, accountsConfig } = useContext( const {
NotificationsCtx save,
) data: _data,
error,
const data = fromNamespace('thirdParty')(_data) accountsConfig
} = useContext(NotificationsCtx)
const filterOptions = type => filterClass(type)(accountsConfig || [])
const data = fromNamespace('thirdParty')(_data)
const getDisplayName = type => it =>
R.compose( const filterOptions = type => filterClass(type)(accountsConfig || [])
R.prop('display'),
R.find(R.propEq('code', it)) const getDisplayName = type => it =>
)(filterOptions(type)) R.compose(
R.prop('display'),
const innerSave = async value => { R.find(R.propEq('code', it))
const config = toNamespace('thirdParty')(value?.thirdParty[0]) )(filterOptions(type))
await save('thirdParty', config)
} const innerSave = async value => {
const config = toNamespace('thirdParty')(value?.thirdParty[0])
const ThirdPartySchema = Yup.object().shape({ await save('thirdParty', config)
sms: Yup.string('SMS must be a string').required('SMS is required'), }
email: Yup.string('Email must be a string').required('Email is required')
}) const ThirdPartySchema = Yup.object().shape({
sms: Yup.string('SMS must be a string').required('SMS is required'),
const elements = [ email: Yup.string('Email must be a string').required('Email is required')
{ })
name: 'sms',
size: 'sm', const elements = [
view: getDisplayName('sms'), {
width: 175, name: 'sms',
input: Autocomplete, size: 'sm',
inputProps: { view: getDisplayName('sms'),
options: filterOptions('sms'), width: 175,
valueProp: 'code', input: Autocomplete,
labelProp: 'display' inputProps: {
} options: filterOptions('sms'),
}, valueProp: 'code',
{ labelProp: 'display'
name: 'email', }
size: 'sm', },
view: getDisplayName('email'), {
width: 175, name: 'email',
input: Autocomplete, size: 'sm',
inputProps: { view: getDisplayName('email'),
options: filterOptions('email'), width: 175,
valueProp: 'code', input: Autocomplete,
labelProp: 'display' inputProps: {
} options: filterOptions('email'),
} valueProp: 'code',
] labelProp: 'display'
const values = { }
sms: data.sms ?? 'twilio', }
email: data.email ?? 'mailgun' ]
} const values = {
sms: data.sms ?? 'twilio',
return ( email: data.email ?? 'mailgun'
<EditableTable }
name="thirdParty"
initialValues={values} return (
data={R.of(values)} <EditableTable
error={error?.message} name="thirdParty"
enableEdit initialValues={values}
editWidth={174} data={R.of(values)}
save={innerSave} error={error?.message}
validationSchema={ThirdPartySchema} enableEdit
elements={elements} editWidth={174}
/> save={innerSave}
) validationSchema={ThirdPartySchema}
} elements={elements}
/>
export default ThirdPartyProvider )
}
export default ThirdPartyProvider

View file

@ -61,7 +61,7 @@ const DISABLE_SMS_NOTICE = gql`
const multiReplace = (str, obj) => { const multiReplace = (str, obj) => {
var re = new RegExp(Object.keys(obj).join('|'), 'gi') var re = new RegExp(Object.keys(obj).join('|'), 'gi')
return str.replace(re, function(matched) { return str.replace(re, function (matched) {
return obj[matched.toLowerCase()] return obj[matched.toLowerCase()]
}) })
} }
@ -82,7 +82,8 @@ const formatContent = content => {
const TOOLTIPS = { const TOOLTIPS = {
smsCode: ``, smsCode: ``,
cashOutDispenseReady: ``, cashOutDispenseReady: ``,
smsReceipt: formatContent(`The contents of this notice will be appended to the end of the SMS receipt sent, and not replace it.\n smsReceipt:
formatContent(`The contents of this notice will be appended to the end of the SMS receipt sent, and not replace it.\n
To edit the contents of the SMS receipt, please go to the 'Receipt' tab`) To edit the contents of the SMS receipt, please go to the 'Receipt' tab`)
} }
@ -124,9 +125,8 @@ const SMSNotices = () => {
const [previewCoords, setPreviewCoords] = useState({ x: 0, y: 0 }) const [previewCoords, setPreviewCoords] = useState({ x: 0, y: 0 })
const [errorMsg, setErrorMsg] = useState('') const [errorMsg, setErrorMsg] = useState('')
const { data: messagesData, loading: messagesLoading } = useQuery( const { data: messagesData, loading: messagesLoading } =
GET_SMS_NOTICES useQuery(GET_SMS_NOTICES)
)
const timezone = R.path(['config', 'locale_timezone'])(messagesData) const timezone = R.path(['config', 'locale_timezone'])(messagesData)

View file

@ -44,9 +44,7 @@ const PREFILL = {
}) })
}, },
cashOutDispenseReady: { cashOutDispenseReady: {
validator: Yup.string() validator: Yup.string().required('The message content is required!').trim()
.required('The message content is required!')
.trim()
}, },
smsReceipt: { smsReceipt: {
validator: Yup.string().trim() validator: Yup.string().trim()
@ -89,9 +87,7 @@ const SMSNoticesModal = ({
event: Yup.string().required('An event is required!'), event: Yup.string().required('An event is required!'),
message: message:
PREFILL[sms?.event]?.validator ?? PREFILL[sms?.event]?.validator ??
Yup.string() Yup.string().required('The message content is required!').trim()
.required('The message content is required!')
.trim()
}) })
const handleSubmit = values => { const handleSubmit = values => {
@ -155,33 +151,29 @@ const SMSNoticesModal = ({
<Info2 noMargin>Values to attach</Info2> <Info2 noMargin>Values to attach</Info2>
)} )}
<div className={classes.chipButtons}> <div className={classes.chipButtons}>
{R.splitEvery(3, CHIPS[sms?.event]).map( {R.splitEvery(3, CHIPS[sms?.event]).map((it, idx) => (
(it, idx) => ( <div key={idx}>
<div key={idx}> {it.map((ite, idx2) => (
{it.map( <Chip
(ite, idx2) => ( key={idx2}
<Chip label={ite.display}
key={idx2} size="small"
label={ite.display} style={{ backgroundColor: zircon }}
size="small" disabled={R.includes(ite.code, values.message)}
style={{ backgroundColor: zircon }} className={classes.chip}
disabled={R.includes(ite.code, values.message)} onClick={() => {
className={classes.chip} setFieldValue(
onClick={() => { 'message',
setFieldValue( values.message.concat(
'message', R.last(values.message) === ' ' ? '' : ' ',
values.message.concat( ite.code
R.last(values.message) === ' ' ? '' : ' ', )
ite.code )
) }}
) />
}} ))}
/> </div>
) ))}
)}
</div>
),
)}
</div> </div>
<div className={classes.footer}> <div className={classes.footer}>
{getErrorMsg(errors, touched, creationError) && ( {getErrorMsg(errors, touched, creationError) && (

View file

@ -1,67 +1,64 @@
import { ALL_CRYPTOS } from '@lamassu/coins/config/consts' import { ALL_CRYPTOS } from '@lamassu/coins/config/consts'
import { getEquivalentCode } from '@lamassu/coins/lightUtils' import { getEquivalentCode } from '@lamassu/coins/lightUtils'
import * as R from 'ramda' import * as R from 'ramda'
const WARNING_LEVELS = { const WARNING_LEVELS = {
CLEAN: 'clean', CLEAN: 'clean',
PARTIAL: 'partial', PARTIAL: 'partial',
IMPORTANT: 'important' IMPORTANT: 'important'
} }
const secretTest = (secret, message) => ({ const secretTest = (secret, message) => ({
name: 'secret-test', name: 'secret-test',
message: message ? `The ${message} is invalid` : 'Invalid field', message: message ? `The ${message} is invalid` : 'Invalid field',
test(val) { test(val) {
if (R.isNil(secret) && R.isNil(val)) { if (R.isNil(secret) && R.isNil(val)) {
return this.createError() return this.createError()
} }
return true return true
} }
}) })
const leadingZerosTest = (value, context) => { const leadingZerosTest = (value, context) => {
if ( if (
R.startsWith('0', context.originalValue) && R.startsWith('0', context.originalValue) &&
R.length(context.originalValue) > 1 R.length(context.originalValue) > 1
) { ) {
return context.createError() return context.createError()
} }
return true return true
} }
const buildCurrencyOptions = markets => { const buildCurrencyOptions = markets => {
const prunedCoins = R.compose( const prunedCoins = R.compose(R.uniq, R.map(getEquivalentCode))(ALL_CRYPTOS)
R.uniq, return R.map(it => {
R.map(getEquivalentCode) const unavailableCryptos = R.difference(prunedCoins, markets[it])
)(ALL_CRYPTOS) const unavailableCryptosFiltered = R.difference(unavailableCryptos, [it]) // As the markets can have stablecoins to trade against other crypto, filter them out, as there can't be pairs such as USDT/USDT
return R.map(it => {
const unavailableCryptos = R.difference(prunedCoins, markets[it]) const unavailableMarketsStr =
const unavailableCryptosFiltered = R.difference(unavailableCryptos, [it]) // As the markets can have stablecoins to trade against other crypto, filter them out, as there can't be pairs such as USDT/USDT R.length(unavailableCryptosFiltered) > 1
? `${R.join(
const unavailableMarketsStr = ', ',
R.length(unavailableCryptosFiltered) > 1 R.slice(0, -1, unavailableCryptosFiltered)
? `${R.join( )} and ${R.last(unavailableCryptosFiltered)}`
', ', : unavailableCryptosFiltered[0]
R.slice(0, -1, unavailableCryptosFiltered)
)} and ${R.last(unavailableCryptosFiltered)}` const warningLevel = R.isEmpty(unavailableCryptosFiltered)
: unavailableCryptosFiltered[0] ? WARNING_LEVELS.CLEAN
: !R.isEmpty(unavailableCryptosFiltered) &&
const warningLevel = R.isEmpty(unavailableCryptosFiltered) R.length(unavailableCryptosFiltered) < R.length(prunedCoins)
? WARNING_LEVELS.CLEAN ? WARNING_LEVELS.PARTIAL
: !R.isEmpty(unavailableCryptosFiltered) && : WARNING_LEVELS.IMPORTANT
R.length(unavailableCryptosFiltered) < R.length(prunedCoins)
? WARNING_LEVELS.PARTIAL return {
: WARNING_LEVELS.IMPORTANT code: R.toUpper(it),
display: R.toUpper(it),
return { warning: warningLevel,
code: R.toUpper(it), warningMessage: !R.isEmpty(unavailableCryptosFiltered)
display: R.toUpper(it), ? `No market pairs available for ${unavailableMarketsStr}`
warning: warningLevel, : `All market pairs are available`
warningMessage: !R.isEmpty(unavailableCryptosFiltered) }
? `No market pairs available for ${unavailableMarketsStr}` }, R.keys(markets))
: `All market pairs are available` }
}
}, R.keys(markets)) export { secretTest, leadingZerosTest, buildCurrencyOptions }
}
export { secretTest, leadingZerosTest, buildCurrencyOptions }

View file

@ -132,14 +132,10 @@ const DetailsRow = ({ it: tx, timezone }) => {
Number.parseFloat(tx.commissionPercentage, 2) * 100 Number.parseFloat(tx.commissionPercentage, 2) * 100
).toFixed(2, 1) // ROUND_DOWN ).toFixed(2, 1) // ROUND_DOWN
const fixedFee = Number.parseFloat(tx.fixedFee) || 0 const fixedFee = Number.parseFloat(tx.fixedFee) || 0
const fiat = BigNumber(tx.fiat) const fiat = BigNumber(tx.fiat).minus(fixedFee).toFixed(2, 1) // ROUND_DOWN
.minus(fixedFee)
.toFixed(2, 1) // ROUND_DOWN
const crypto = getCryptoAmount(tx) const crypto = getCryptoAmount(tx)
const cryptoFee = tx.fee ? `${getCryptoFeeAmount(tx)} ${tx.fiatCode}` : 'N/A' const cryptoFee = tx.fee ? `${getCryptoFeeAmount(tx)} ${tx.fiatCode}` : 'N/A'
const exchangeRate = BigNumber(fiat) const exchangeRate = BigNumber(fiat).div(crypto).toFixed(2, 1) // ROUND_DOWN
.div(crypto)
.toFixed(2, 1) // ROUND_DOWN
const displayExRate = `1 ${tx.cryptoCode} = ${exchangeRate} ${tx.fiatCode}` const displayExRate = `1 ${tx.cryptoCode} = ${exchangeRate} ${tx.fiatCode}`
const discount = tx.discount ? `-${tx.discount}%` : null const discount = tx.discount ? `-${tx.discount}%` : null
@ -203,23 +199,22 @@ const DetailsRow = ({ it: tx, timezone }) => {
<div className={classes.walletScore}> <div className={classes.walletScore}>
<svg width={103} height={10}> <svg width={103} height={10}>
{R.range(0, 10).map((it, idx) => ( {R.range(0, 10).map((it, idx) => (
<circle <circle
key={idx} key={idx}
cx={it * 10 + 6} cx={it * 10 + 6}
cy={4} cy={4}
r={3.5} r={3.5}
fill={ fill={
it < tx.walletScore it < tx.walletScore
? !hasChainAnalysisError(tx) ? !hasChainAnalysisError(tx)
? primaryColor ? primaryColor
: errorColor : errorColor
: !hasChainAnalysisError(tx) : !hasChainAnalysisError(tx)
? subheaderColor ? subheaderColor
: offErrorColor : offErrorColor
} }
/> />
) ))}
)}
</svg> </svg>
<P <P
noMargin noMargin

View file

@ -65,9 +65,7 @@ const ChooseType = () => {
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
inputType: Yup.string() inputType: Yup.string().label('Input type').required()
.label('Input type')
.required()
}) })
const defaultValues = { const defaultValues = {

View file

@ -31,12 +31,8 @@ const Screen1Information = () => {
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
screen1Title: Yup.string() screen1Title: Yup.string().label('Screen title').required(),
.label('Screen title') screen1Text: Yup.string().label('Screen text').required()
.required(),
screen1Text: Yup.string()
.label('Screen text')
.required()
}) })
const defaultValues = { const defaultValues = {

View file

@ -29,12 +29,8 @@ const ScreenInformation = () => {
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
screen2Title: Yup.string() screen2Title: Yup.string().label('Screen title').required(),
.label('Screen title') screen2Text: Yup.string().label('Screen text').required()
.required(),
screen2Text: Yup.string()
.label('Screen text')
.required()
}) })
const defaultValues = { const defaultValues = {

View file

@ -40,38 +40,27 @@ const validationSchema = Yup.lazy(values => {
switch (values.inputType) { switch (values.inputType) {
case 'numerical': case 'numerical':
return Yup.object({ return Yup.object({
constraintType: Yup.string() constraintType: Yup.string().label('Constraint type').required(),
.label('Constraint type')
.required(),
inputLength: Yup.number().when('constraintType', { inputLength: Yup.number().when('constraintType', {
is: 'length', is: 'length',
then: schema => schema then: schema =>
.min(0) schema.min(0).required('The number of digits is required'),
.required('The number of digits is required'),
otherwise: schema => schema.mixed().notRequired() otherwise: schema => schema.mixed().notRequired()
}) })
}) })
case 'text': case 'text':
return Yup.object({ return Yup.object({
constraintType: Yup.string() constraintType: Yup.string().label('Constraint type').required(),
.label('Constraint type') inputLabel1: Yup.string().label('Text entry label').required(),
.required(),
inputLabel1: Yup.string()
.label('Text entry label')
.required(),
inputLabel2: Yup.string().when('constraintType', { inputLabel2: Yup.string().when('constraintType', {
is: 'spaceSeparation', is: 'spaceSeparation',
then: schema => schema then: schema => schema.label('Second word label').required(),
.label('Second word label')
.required(),
otherwise: schema => schema.mixed().notRequired() otherwise: schema => schema.mixed().notRequired()
}) })
}) })
case 'choiceList': case 'choiceList':
return Yup.object({ return Yup.object({
constraintType: Yup.string() constraintType: Yup.string().label('Constraint type').required(),
.label('Constraint type')
.required(),
listChoices: Yup.array().test( listChoices: Yup.array().test(
'has-2-or-more', 'has-2-or-more',
'Choice list needs to have two or more non empty fields', 'Choice list needs to have two or more non empty fields',

View file

@ -65,9 +65,8 @@ const Triggers = () => {
const classes = useStyles() const classes = useStyles()
const [wizardType, setWizard] = useState(false) const [wizardType, setWizard] = useState(false)
const { data, loading: configLoading, refetch } = useQuery(GET_CONFIG) const { data, loading: configLoading, refetch } = useQuery(GET_CONFIG)
const { data: customInfoReqData, loading: customInfoLoading } = useQuery( const { data: customInfoReqData, loading: customInfoLoading } =
GET_CUSTOM_REQUESTS useQuery(GET_CUSTOM_REQUESTS)
)
const [error, setError] = useState(null) const [error, setError] = useState(null)
const [subMenu, setSubMenu] = useState(false) const [subMenu, setSubMenu] = useState(false)

View file

@ -45,9 +45,8 @@ const AdvancedTriggersSettings = memo(() => {
const [isEditingOverrides, setEditingOverrides] = useState(false) const [isEditingOverrides, setEditingOverrides] = useState(false)
const { data, loading: configLoading } = useQuery(GET_INFO) const { data, loading: configLoading } = useQuery(GET_INFO)
const { data: customInfoReqData, loading: customInfoLoading } = useQuery( const { data: customInfoReqData, loading: customInfoLoading } =
GET_CUSTOM_REQUESTS useQuery(GET_CUSTOM_REQUESTS)
)
const customInfoRequests = const customInfoRequests =
R.path(['customInfoRequests'])(customInfoReqData) ?? [] R.path(['customInfoRequests'])(customInfoReqData) ?? []

View file

@ -30,9 +30,7 @@ const displayRequirement = (code, customInfoRequests) => {
} }
const defaultSchema = Yup.object().shape({ const defaultSchema = Yup.object().shape({
expirationTime: Yup.string() expirationTime: Yup.string().label('Expiration time').required(),
.label('Expiration time')
.required(),
automation: Yup.string() automation: Yup.string()
.label('Automation') .label('Automation')
.matches(/(Manual|Automatic)/) .matches(/(Manual|Automatic)/)
@ -60,9 +58,7 @@ const getOverridesSchema = (values, customInfoRequests) => {
return true return true
} }
}), }),
expirationTime: Yup.string() expirationTime: Yup.string().label('Expiration time').required(),
.label('Expiration time')
.required(),
automation: Yup.string() automation: Yup.string()
.label('Automation') .label('Automation')
.matches(/(Manual|Automatic)/) .matches(/(Manual|Automatic)/)

View file

@ -110,9 +110,7 @@ const threshold = Yup.object().shape({
const requirement = Yup.object().shape({ const requirement = Yup.object().shape({
requirement: Yup.string().required(), requirement: Yup.string().required(),
suspensionDays: Yup.number() suspensionDays: Yup.number().transform(transformNumber).nullable()
.transform(transformNumber)
.nullable()
}) })
const Schema = Yup.object() const Schema = Yup.object()
@ -266,12 +264,8 @@ const typeSchema = Yup.object()
'The trigger type is required' 'The trigger type is required'
), ),
threshold: Yup.object({ threshold: Yup.object({
threshold: Yup.number() threshold: Yup.number().transform(transformNumber).nullable(),
.transform(transformNumber) thresholdDays: Yup.number().transform(transformNumber).nullable()
.nullable(),
thresholdDays: Yup.number()
.transform(transformNumber)
.nullable()
}) })
}) })
.test(({ threshold, triggerType }, context) => { .test(({ threshold, triggerType }, context) => {
@ -327,13 +321,8 @@ const typeOptions = [
const Type = ({ ...props }) => { const Type = ({ ...props }) => {
const classes = useStyles() const classes = useStyles()
const { const { errors, touched, values, setTouched, handleChange } =
errors, useFormikContext()
touched,
values,
setTouched,
handleChange
} = useFormikContext()
const typeClass = { const typeClass = {
[classes.error]: errors.triggerType && touched.triggerType [classes.error]: errors.triggerType && touched.triggerType
@ -484,24 +473,16 @@ const requirementSchema = Yup.object()
requirement: Yup.string().required(), requirement: Yup.string().required(),
suspensionDays: Yup.number().when('requirement', { suspensionDays: Yup.number().when('requirement', {
is: value => value === 'suspend', is: value => value === 'suspend',
then: schema => schema then: schema => schema.nullable().transform(transformNumber),
.nullable() otherwise: schema => schema.nullable().transform(() => null)
.transform(transformNumber),
otherwise: schema => schema
.nullable()
.transform(() => null)
}), }),
customInfoRequestId: Yup.string().when('requirement', { customInfoRequestId: Yup.string().when('requirement', {
is: value => value !== 'custom', is: value => value !== 'custom',
then: schema => schema then: schema => schema.nullable().transform(() => '')
.nullable()
.transform(() => '')
}), }),
externalService: Yup.string().when('requirement', { externalService: Yup.string().when('requirement', {
is: value => value !== 'external', is: value => value !== 'external',
then: schema => schema then: schema => schema.nullable().transform(() => '')
.nullable()
.transform(() => '')
}) })
}).required() }).required()
}) })
@ -583,13 +564,8 @@ const Requirement = ({
customInfoRequests = [] customInfoRequests = []
}) => { }) => {
const classes = useStyles() const classes = useStyles()
const { const { touched, errors, values, handleChange, setTouched } =
touched, useFormikContext()
errors,
values,
handleChange,
setTouched
} = useFormikContext()
const isSuspend = values?.requirement?.requirement === 'suspend' const isSuspend = values?.requirement?.requirement === 'suspend'
const isCustom = values?.requirement?.requirement === 'custom' const isCustom = values?.requirement?.requirement === 'custom'
@ -766,9 +742,9 @@ const RequirementInput = ({ customInfoRequests = [] }) => {
R.path(['requirement', 'customInfoRequestId'])(values) ?? '' R.path(['requirement', 'customInfoRequestId'])(values) ?? ''
const isSuspend = requirement === 'suspend' const isSuspend = requirement === 'suspend'
const display = customRequestId const display = customRequestId
? R.path(['customRequest', 'name'])( ? (R.path(['customRequest', 'name'])(
R.find(customReqIdMatches(customRequestId))(customInfoRequests) R.find(customReqIdMatches(customRequestId))(customInfoRequests)
) ?? '' ) ?? '')
: getView(requirementOptions, 'display')(requirement) : getView(requirementOptions, 'display')(requirement)
return ( return (
@ -798,12 +774,12 @@ const RequirementView = ({
const classes = useStyles() const classes = useStyles()
const display = const display =
requirement === 'custom' requirement === 'custom'
? R.path(['customRequest', 'name'])( ? (R.path(['customRequest', 'name'])(
R.find(customReqIdMatches(customInfoRequestId))(customInfoRequests) R.find(customReqIdMatches(customInfoRequestId))(customInfoRequests)
) ?? '' ) ?? '')
: requirement === 'external' : requirement === 'external'
? `External verification (${onlyFirstToUpper(externalService)})` ? `External verification (${onlyFirstToUpper(externalService)})`
: getView(requirementOptions, 'display')(requirement) : getView(requirementOptions, 'display')(requirement)
const isSuspend = requirement === 'suspend' const isSuspend = requirement === 'suspend'
return ( return (
<Box display="flex" alignItems="baseline"> <Box display="flex" alignItems="baseline">

View file

@ -119,11 +119,10 @@ const Routes = () => {
{...transitionProps} {...transitionProps}
in={true} in={true}
mountOnEnter mountOnEnter
unmountOnExit unmountOnExit>
> <div className={classes.wrapper}>
<div className={classes.wrapper}> <Dashboard />
<Dashboard /> </div>
</div>
</Transition> </Transition>
</PrivateRoute> </PrivateRoute>
<PrivateRoute path="/machines" component={Machines} /> <PrivateRoute path="/machines" component={Machines} />
@ -139,19 +138,18 @@ const Routes = () => {
{...transitionProps} {...transitionProps}
in={!!matchPath(location.pathname, { path: route })} in={!!matchPath(location.pathname, { path: route })}
mountOnEnter mountOnEnter
unmountOnExit unmountOnExit>
>
<div className={classes.wrapper}> <div className={classes.wrapper}>
<PrivateRoute path={route} key={key}> <PrivateRoute path={route} key={key}>
<Page name={key}/> <Page name={key} />
</PrivateRoute> </PrivateRoute>
</div> </div>
</Transition> </Transition>
</PrivateRoute> </PrivateRoute>
))} ))}
<PublicRoute path="/404"/> <PublicRoute path="/404" />
<PublicRoute path="*"> <PublicRoute path="*">
<Redirect to={{ pathname: '/404' }}/> <Redirect to={{ pathname: '/404' }} />
</PublicRoute> </PublicRoute>
</Switch> </Switch>
) )

View file

@ -6,7 +6,8 @@ const WALLET_SCORING_DEFAULT_THRESHOLD = 9
const AUTOMATIC = 'automatic' const AUTOMATIC = 'automatic'
const MANUAL = 'manual' const MANUAL = 'manual'
const IP_CHECK_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ const IP_CHECK_REGEX =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
const SWEEPABLE_CRYPTOS = ['ETH'] const SWEEPABLE_CRYPTOS = ['ETH']

View file

@ -16,10 +16,10 @@ const formatName = idCardData => {
firstName && lastName firstName && lastName
? `${R.o(R.toUpper, R.head)(firstName)}. ${lastName}` ? `${R.o(R.toUpper, R.head)(firstName)}. ${lastName}`
: R.isNil(firstName) : R.isNil(firstName)
? lastName ? lastName
: R.isNil(lastName) : R.isNil(lastName)
? firstName ? firstName
: null : null
return idCardData ? innerFormatName(idCardData) : null return idCardData ? innerFormatName(idCardData) : null
} }

View file

@ -20,11 +20,7 @@ const splitOnUpper = R.compose(
R.replace(/([A-Z])/g, ' $1'), R.replace(/([A-Z])/g, ' $1'),
toFirstLower toFirstLower
) )
const startCase = R.compose( const startCase = R.compose(R.join(' '), R.map(onlyFirstToUpper), splitOnUpper)
R.join(' '),
R.map(onlyFirstToUpper),
splitOnUpper
)
const sentenceCase = R.compose(onlyFirstToUpper, R.join(' '), splitOnUpper) const sentenceCase = R.compose(onlyFirstToUpper, R.join(' '), splitOnUpper)

View file

@ -7,6 +7,9 @@ import fixReactVirtualized from 'esbuild-plugin-react-virtualized'
export default defineConfig({ export default defineConfig({
base: '/', base: '/',
build: {
outDir: 'build'
},
server: { server: {
port: 3001, port: 3001,
proxy: { proxy: {
@ -19,18 +22,13 @@ export default defineConfig({
}, },
optimizeDeps: { optimizeDeps: {
esbuildOptions: { esbuildOptions: {
plugins: [ plugins: [fixReactVirtualized]
fixReactVirtualized,
],
} }
}, },
plugins: [ plugins: [react(), svgr()],
react(),
svgr(),
],
resolve: { resolve: {
alias: { alias: {
'src': fileURLToPath(new URL('./src', import.meta.url)) src: fileURLToPath(new URL('./src', import.meta.url))
}, }
}, }
}) })