fix: triggers wizard fields are more responsive to errors, improved UX

This commit is contained in:
Sérgio Salgado 2021-03-29 15:29:06 +01:00 committed by Josh Harvey
parent 44d63337fe
commit 9094569b83
2 changed files with 150 additions and 52 deletions

View file

@ -222,6 +222,42 @@ const Wizard = ({ onClose, save, error, currency }) => {
})
}
const createErrorMessage = (errors, touched, values) => {
const triggerType = values?.triggerType
const containsType = R.contains(triggerType)
const isSuspend = values?.requirement?.requirement === 'suspend'
const hasRequirementError =
!!errors.requirement?.suspensionDays &&
!!touched.requirement?.suspensionDays &&
(!values.requirement?.suspensionDays ||
values.requirement?.suspensionDays < 0)
const hasAmountError =
!!errors.threshold &&
!!touched.threshold?.threshold &&
!containsType(['consecutiveDays']) &&
(!values.threshold?.threshold || values.threshold?.threshold < 0)
const hasDaysError =
!!errors.threshold &&
!!touched.threshold?.thresholdDays &&
!containsType(['txAmount']) &&
(!values.threshold?.thresholdDays || values.threshold?.thresholdDays < 0)
if (containsType(['txAmount', 'txVolume', 'txVelocity']) && hasAmountError)
return errors.threshold
if (
containsType(['txVolume', 'txVelocity', 'consecutiveDays']) &&
hasDaysError
)
return errors.threshold
if (isSuspend && hasRequirementError)
return errors.requirement?.suspensionDays
}
return (
<>
<Modal
@ -246,21 +282,28 @@ const Wizard = ({ onClose, save, error, currency }) => {
/>
<Formik
validateOnBlur={false}
validateOnChange={false}
validateOnChange={true}
enableReinitialize
onSubmit={onContinue}
initialValues={stepOptions.initialValues}
validationSchema={stepOptions.schema}>
{({ errors, touched, values }) => (
<Form className={classes.form}>
<GetValues setValues={setLiveValues} />
<stepOptions.Component {...stepOptions.props} />
<div className={classes.submit}>
{error && <ErrorMessage>Failed to save</ErrorMessage>}
{createErrorMessage(errors, touched, values) && (
<ErrorMessage>
{createErrorMessage(errors, touched, values)}
</ErrorMessage>
)}
<Button className={classes.button} type="submit">
{isLastStep ? 'Finish' : 'Next'}
</Button>
</div>
</Form>
)}
</Formik>
</Modal>
</>

View file

@ -258,11 +258,33 @@ const typeSchema = Yup.object()
.nullable()
})
})
.test(
'are-fields-set',
'All fields must be set.',
({ triggerType, threshold }, context) => {
const validator = {
.test(({ threshold, triggerType }, context) => {
console.log(context)
const errorMessages = {
txAmount: threshold => 'Amount must be greater than or equal to 0',
txVolume: threshold => {
const thresholdMessage = 'Volume must be greater than or equal to 0'
const thresholdDaysMessage = 'Days must be greater than 0'
const message = []
if (!threshold.threshold || threshold.threshold < 0)
message.push(thresholdMessage)
if (!threshold.thresholdDays || threshold.thresholdDays <= 0)
message.push(thresholdDaysMessage)
return message.join(', ')
},
txVelocity: threshold => {
const thresholdMessage = 'Transactions must be greater than 0'
const thresholdDaysMessage = 'Days must be greater than 0'
const message = []
if (!threshold.threshold || threshold.threshold <= 0)
message.push(thresholdMessage)
if (!threshold.thresholdDays || threshold.thresholdDays <= 0)
message.push(thresholdDaysMessage)
return message.join(', ')
},
consecutiveDays: threshold => 'Days must be greater than 0'
}
const thresholdValidator = {
txAmount: threshold => threshold.threshold >= 0,
txVolume: threshold =>
threshold.threshold >= 0 && threshold.thresholdDays > 0,
@ -271,12 +293,13 @@ const typeSchema = Yup.object()
consecutiveDays: threshold => threshold.thresholdDays > 0
}
return (
(triggerType && validator?.[triggerType](threshold)) ||
context.createError({ path: 'threshold' })
)
}
)
if (triggerType && thresholdValidator[triggerType](threshold)) return
return context.createError({
path: 'threshold',
message: errorMessages[triggerType](threshold)
})
})
const typeOptions = [
{ display: 'Transaction amount', code: 'txAmount' },
@ -299,11 +322,21 @@ const Type = ({ ...props }) => {
const isThresholdDaysEnabled = containsType(['txVolume', 'txVelocity'])
const isConsecutiveDaysEnabled = containsType(['consecutiveDays'])
const hasAmountError =
!!errors.threshold &&
!!touched.threshold?.threshold &&
!isConsecutiveDaysEnabled &&
(!values.threshold?.threshold || values.threshold?.threshold < 0)
const hasDaysError =
!!errors.threshold &&
!!touched.threshold?.thresholdDays &&
!containsType(['txAmount']) &&
(!values.threshold?.thresholdDays || values.threshold?.thresholdDays < 0)
const triggerTypeError = !!(hasDaysError || hasAmountError)
const thresholdClass = {
[classes.error]:
errors.threshold &&
((!containsType(['consecutiveDays']) && touched.threshold?.threshold) ||
(!containsType(['txAmount']) && touched.threshold?.thresholdDays))
[classes.error]: triggerTypeError
}
const isRadioGroupActive = () => {
@ -343,6 +376,7 @@ const Type = ({ ...props }) => {
component={NumberInput}
size="lg"
name="threshold.threshold"
error={hasAmountError}
/>
<Info1 className={classnames(classes.description)}>
{props.currency}
@ -356,6 +390,7 @@ const Type = ({ ...props }) => {
component={NumberInput}
size="lg"
name="threshold.threshold"
error={hasAmountError}
/>
<Info1 className={classnames(classes.description)}>
transactions
@ -377,6 +412,7 @@ const Type = ({ ...props }) => {
component={NumberInput}
size="lg"
name="threshold.thresholdDays"
error={hasDaysError}
/>
<Info1 className={classnames(classes.description)}>days</Info1>
</>
@ -388,6 +424,7 @@ const Type = ({ ...props }) => {
component={NumberInput}
size="lg"
name="threshold.thresholdDays"
error={hasDaysError}
/>
<Info1 className={classnames(classes.description)}>
consecutive days
@ -411,7 +448,8 @@ const type = currency => ({
}
})
const requirementSchema = Yup.object().shape({
const requirementSchema = Yup.object()
.shape({
requirement: Yup.object({
requirement: Yup.string().required(),
suspensionDays: Yup.number().when('requirement', {
@ -425,6 +463,19 @@ const requirementSchema = Yup.object().shape({
})
}).required()
})
.test(({ requirement }, context) => {
const requirementValidator = requirement =>
requirement.requirement === 'suspend'
? requirement.suspensionDays > 0
: true
if (requirement && requirementValidator(requirement)) return
return context.createError({
path: 'requirement',
message: 'Suspension days must be greater than 0'
})
})
const requirementOptions = [
{ display: 'SMS verification', code: 'sms' },
@ -442,17 +493,20 @@ const Requirement = () => {
const classes = useStyles()
const { touched, errors, values } = useFormikContext()
const hasRequirementError =
!!errors.requirement?.suspensionDays &&
!!touched.requirement?.suspensionDays &&
(!values.requirement?.suspensionDays ||
values.requirement?.suspensionDays < 0)
const isSuspend = values?.requirement?.requirement === 'suspend'
const titleClass = {
[classes.error]:
!R.isEmpty(R.omit(['suspensionDays'], errors.requirement)) ||
(errors.requirement &&
touched.requirement &&
errors.requirement.suspensionDays &&
touched.requirement.suspensionDays)
(isSuspend && hasRequirementError)
}
const isSuspend = values?.requirement?.requirement === 'suspend'
return (
<>
<Box display="flex" alignItems="center">
@ -474,6 +528,7 @@ const Requirement = () => {
label="Days"
size="lg"
name="requirement.suspensionDays"
error={hasRequirementError}
/>
)}
</>