diff --git a/packages/web/src/components/ControlledAutocomplete/index.jsx b/packages/web/src/components/ControlledAutocomplete/index.jsx
index 0ade2237..ebb33beb 100644
--- a/packages/web/src/components/ControlledAutocomplete/index.jsx
+++ b/packages/web/src/components/ControlledAutocomplete/index.jsx
@@ -29,6 +29,8 @@ function ControlledAutocomplete(props) {
options = [],
dependsOn = [],
showOptionValue,
+ renderInput,
+ showHelperText = true,
...autocompleteProps
} = props;
let dependsOnValues = [];
@@ -105,16 +107,18 @@ function ControlledAutocomplete(props) {
)}
)}
+ renderInput={(params) => renderInput(params, fieldState)}
/>
-
-
- {fieldState.isTouched
- ? fieldState.error?.message || description
- : description}
-
+ {showHelperText && (
+
+ {fieldState.isTouched
+ ? fieldState.error?.message || description
+ : description}
+
+ )}
)}
/>
@@ -132,6 +136,8 @@ ControlledAutocomplete.propTypes = {
onBlur: PropTypes.func,
onChange: PropTypes.func,
options: PropTypes.array,
+ renderInput: PropTypes.func.isRequired,
+ showHelperText: PropTypes.bool,
};
export default ControlledAutocomplete;
diff --git a/packages/web/src/components/Form/index.jsx b/packages/web/src/components/Form/index.jsx
index 574674d4..614873f7 100644
--- a/packages/web/src/components/Form/index.jsx
+++ b/packages/web/src/components/Form/index.jsx
@@ -13,12 +13,14 @@ function Form(props) {
resolver,
render,
mode = 'all',
+ reValidateMode = 'onBlur',
+ automaticValidation = true,
...formProps
} = props;
const methods = useForm({
defaultValues,
- reValidateMode: 'onBlur',
+ reValidateMode,
resolver,
mode,
});
@@ -30,7 +32,9 @@ function Form(props) {
* For fields having `dependsOn` fields, we need to re-validate the form.
*/
React.useEffect(() => {
- methods.trigger();
+ if (automaticValidation) {
+ methods.trigger();
+ }
}, [methods.trigger, form]);
React.useEffect(() => {
@@ -56,6 +60,8 @@ Form.propTypes = {
render: PropTypes.func,
resolver: PropTypes.func,
mode: PropTypes.oneOf(['onChange', 'onBlur', 'onSubmit', 'onTouched', 'all']),
+ reValidateMode: PropTypes.oneOf(['onChange', 'onBlur', 'onSubmit']),
+ automaticValidation: PropTypes.bool,
};
export default Form;
diff --git a/packages/web/src/components/TextField/index.jsx b/packages/web/src/components/TextField/index.jsx
index 20ec3aeb..dfe505c2 100644
--- a/packages/web/src/components/TextField/index.jsx
+++ b/packages/web/src/components/TextField/index.jsx
@@ -31,6 +31,7 @@ function TextField(props) {
onBlur,
onChange,
'data-test': dataTest,
+ showError = false,
...textFieldProps
} = props;
return (
@@ -47,6 +48,7 @@ function TextField(props) {
onBlur: controllerOnBlur,
...field
},
+ fieldState: { error },
}) => (
)}
/>
@@ -89,6 +92,7 @@ TextField.propTypes = {
disabled: PropTypes.bool,
onBlur: PropTypes.func,
onChange: PropTypes.func,
+ showError: PropTypes.bool,
};
export default TextField;
diff --git a/packages/web/src/pages/Authentication/RoleMappings.jsx b/packages/web/src/pages/Authentication/RoleMappings.jsx
index 9bfff4db..66a14c38 100644
--- a/packages/web/src/pages/Authentication/RoleMappings.jsx
+++ b/packages/web/src/pages/Authentication/RoleMappings.jsx
@@ -5,6 +5,8 @@ import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
import { useMemo } from 'react';
+import { yupResolver } from '@hookform/resolvers/yup';
+import * as yup from 'yup';
import Form from 'components/Form';
import useFormatMessage from 'hooks/useFormatMessage';
@@ -23,6 +25,42 @@ function generateFormRoleMappings(roleMappings) {
}));
}
+const uniqueRemoteRoleName = (array, context, formatMessage) => {
+ const seen = new Set();
+ for (const [index, value] of array.entries()) {
+ if (seen.has(value.remoteRoleName)) {
+ const path = `${context.path}[${index}].remoteRoleName`;
+ return context.createError({
+ message: `${formatMessage('roleMappingsForm.remoteRoleName')} must be unique`,
+ path,
+ });
+ }
+ seen.add(value.remoteRoleName);
+ }
+ return true;
+};
+
+const getValidationSchema = (formatMessage) =>
+ yup.object({
+ roleMappings: yup
+ .array()
+ .of(
+ yup.object({
+ roleId: yup
+ .string()
+ .required(`${formatMessage('roleMappingsForm.role')} is required`),
+ remoteRoleName: yup
+ .string()
+ .required(
+ `${formatMessage('roleMappingsForm.remoteRoleName')} is required`,
+ ),
+ }),
+ )
+ .test('unique-remoteRoleName', '', (value, ctx) => {
+ return uniqueRemoteRoleName(value, ctx, formatMessage);
+ }),
+ });
+
function RoleMappings({ provider, providerLoading }) {
const formatMessage = useFormatMessage();
const enqueueSnackbar = useEnqueueSnackbar();
@@ -94,7 +132,15 @@ function RoleMappings({ provider, providerLoading }) {
{formatMessage('roleMappingsForm.title')}
-