diff --git a/app/javascript/components/settings-users-form/helper.js b/app/javascript/components/settings-users-form/helper.js
new file mode 100644
index 00000000000..f130b114f33
--- /dev/null
+++ b/app/javascript/components/settings-users-form/helper.js
@@ -0,0 +1,24 @@
+import React, { useState } from 'react';
+import { Button } from 'carbon-components-react';
+
+/** button component used as a mapper to change the password */
+export const ChangePasswordButton = (props) => {
+ const { newRecord, enableConfirmPassword, isConfirmPasswordEnabled } = props;
+ const [isChangingPassword, setIsChangingPassword] = useState(false);
+
+ const handleClick = () => {
+ /**Enable change password button if its not a new record */
+ if (!newRecord) {
+ enableConfirmPassword(!isConfirmPasswordEnabled);
+ setIsChangingPassword(!isChangingPassword);
+ }
+ };
+
+ return !newRecord ? (
+
+
+
+ ) : null;
+};
diff --git a/app/javascript/components/settings-users-form/index.jsx b/app/javascript/components/settings-users-form/index.jsx
new file mode 100644
index 00000000000..877b76a0d89
--- /dev/null
+++ b/app/javascript/components/settings-users-form/index.jsx
@@ -0,0 +1,134 @@
+import React, { useState, useEffect } from 'react';
+import MiqFormRenderer from '@@ddf';
+import PropTypes from 'prop-types';
+import createSchema from './schema';
+import { resources } from '../../spec/schedule-form/data';
+import handleFailure from '../../helpers/handle-failure';
+import componentMapper from '../../forms/mappers/componentMapper';
+import { ChangePasswordButton } from './helper';
+
+const SettingsUsersForm = ({ recordId }) => {
+ const newRecord = recordId === 'new';
+
+ const [data, setData] = useState({
+ isLoading: !!recordId,
+ groups: [],
+ initialValues: undefined,
+ userid: '',
+ });
+
+
+ const enableConfirmPassword = (enable) => {
+ setIsConfirmPasswordEnabled(enable);
+ };
+
+ /** State variable to control the Confirm Password field */
+ const [isConfirmPasswordEnabled, setIsConfirmPasswordEnabled] = useState(!newRecord);
+
+ const mapper = {
+ ...componentMapper,
+ 'changePassword': ChangePasswordButton,
+ };
+
+ const validatorMapper = {
+ 'same-password': () => (value, allValues) => value !== allValues.password ? 'Password do not match' : undefined
+ }
+
+ const redirectUrl = (newRecord, button) => `/ops/settings_users_helper/${newRecord}?button=${button}`;
+
+ const USER_ACTIONS = {
+ CREATE: 'create',
+ SAVE: 'save',
+ CANCEL: 'cancel',
+ RESET: 'reset',
+ };
+
+ /** Generate dropdown options from resources */
+ const groupOptions = (resources) => resources.map((item) => ({label: item.description, value: item.id}))
+
+ /** Fetch data from the API on component mount */
+ useEffect(() => {
+ if (recordId && !newRecord) {
+ Promise.all([
+ API.get(`/api/groups?expand=resources`),
+ API.get(`/api/users/${recordId}?expand=resources/`)])
+ .then(([groups, users]) => {
+ console.log(groups,"groupid");
+ console.log(users,"userid");
+ setData({
+ ...data,
+ isLoading: false,
+ groups: groupOptions(groups.resources),
+ initialValues: users,
+ userid: users.userid || '',
+ });
+ });
+ } else {
+ API.get(`/api/groups?expand=resources`).then(({resources}) => {
+ setData({
+ ...data,
+ groups: groupOptions(resources),
+ isLoading: false
+ });
+ })
+ }
+ }, [recordId]);
+
+ console.log(data,"initial values")
+
+ /** Function to handle form submission */
+ const onSubmit = (values, newRecord, recordId) => {
+ const userPayload = {
+ userid: values.userid,
+ password: values.password,
+ name: values.name,
+ email:values.email,
+ // group: { id:20},
+ group: values.current_group_id,
+ };
+
+ console.log(values,"values")
+ const url = newRecord ? '/api/users' : `/api/users/${recordId}`;
+ API.post(url, userPayload, {
+ skipErrors: [400, 500]
+ })
+ .then((response) => {
+ const createdRecordId = newRecord ? response.id : recordId;
+ window.miqJqueryRequest(redirectUrl(createdRecordId));
+ })
+ .catch(handleFailure);
+
+ };
+
+ const onCancel = () => {
+ /** If it's a new record, redirect to the previous page */
+ if (newRecord) {
+ window.miqJqueryRequest(redirectUrl(recordId, USER_ACTIONS.CANCEL));
+ } else {
+ /** For an existing user, reset the form values to the initial state */
+ setData({ initialValues, isLoading: false });
+ }
+ };
+
+ return !data.isLoading && (
+ }}
+ onSubmit={onSubmit}
+ onCancel={() => onCancel()}
+ canReset={!newRecord}
+ buttonsLabels={{
+ submitLabel: newRecord ? __('Add') : __('Save')
+ }}
+ validatorMapper={validatorMapper}
+ />
+ );
+};
+
+SettingsUsersForm.propTypes = {
+ recordId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
+};
+
+export default SettingsUsersForm;
diff --git a/app/javascript/components/settings-users-form/schema.js b/app/javascript/components/settings-users-form/schema.js
new file mode 100644
index 00000000000..be09294d019
--- /dev/null
+++ b/app/javascript/components/settings-users-form/schema.js
@@ -0,0 +1,95 @@
+import { componentTypes, validatorTypes } from '@@ddf';
+import FormSpy from '@data-driven-forms/react-form-renderer/form-spy';
+
+const formSchema = (newRecord, groups, userid, isConfirmPasswordEnabled ) => ({
+ fields: [
+ {
+ component: componentTypes.SUB_FORM,
+ id: 'name-wrapper',
+ name: 'subform-1',
+ title: __('User Information'),
+ fields: [
+ {
+ component: componentTypes.TEXT_FIELD,
+ id: 'name',
+ name: 'name',
+ label: __('Full Name'),
+ maxLength: 50,
+ // validate: [{ type: validatorTypes.REQUIRED }],
+ isRequired: newRecord,
+ isDisabled: userid === 'admin',
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ id: 'userid',
+ name: 'userid',
+ label: __('Username'),
+ maxLength: 50,
+ isRequired: newRecord,
+ isDisabled: userid === 'admin',
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ id: 'password',
+ name: 'password',
+ // type: 'password',
+ label: __('Password'),
+ placeholder: newRecord ? ' ' : '●●●●●●●●',
+ maxLength: 50,
+ isRequired: newRecord,
+ isDisabled: !newRecord && isConfirmPasswordEnabled, // Disable when not a new record or isConfirmPasswordEnabled is false
+ },
+ {
+ component: 'changePassword',
+ id: 'changePassword',
+ name: 'changePassword',
+ label: __('Change Password'),
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ id: 'verify',
+ name: 'verify',
+ type: 'password',
+ maxLength: 50,
+ validate: [
+ { type: 'same-password', errorText: 'Passwords do not match' } // Add an error message
+ ],
+ label: __('Confirm Password'),
+ isRequired: newRecord,
+ condition: {
+ when: 'password',
+ isNotEmpty: true
+ }
+ },
+ {
+ component: componentTypes.TEXT_FIELD,
+ id: 'email',
+ name: 'email',
+ label: __('E-mail Address'),
+ maxLength: 253,
+ autoComplete: 'off',
+ validate: [
+ {
+ type: 'pattern',
+ pattern: '[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$',
+ message: 'Invalid email format',
+ },
+ ],
+ isRequired: newRecord,
+ },
+ {
+ component: componentTypes.SELECT,
+ id: 'available_groups',
+ name: 'available_groups',
+ label: __('Available Groups'),
+ placeholder: __('Choose one or more Groups'),
+ isRequired: newRecord,
+ // isMulti: true,
+ options: groups,
+ },
+ ],
+ },
+ ],
+});
+
+export default formSchema;
diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js
index 8ea69b9092e..30fcae19710 100644
--- a/app/javascript/packs/component-definitions-common.js
+++ b/app/javascript/packs/component-definitions-common.js
@@ -161,6 +161,7 @@ import StorageServiceForm from '../components/storage-service-form';
import VolumeMappingForm from '../components/volume-mapping-form';
import ZoneForm from '../components/zone-form';
import AnsiblePlayBookEditCatalogForm from '../components/ansible-playbook-edit-catalog-form';
+import SettingsUsersForm from '../components/settings-users-form';
/**
* Add component definitions to this file.
@@ -331,3 +332,4 @@ ManageIQ.component.addReact('VolumeMappingForm', VolumeMappingForm);
ManageIQ.component.addReact('VmCommonRenameForm', VmCommonRenameForm);
ManageIQ.component.addReact('ZoneForm', ZoneForm);
ManageIQ.component.addReact('AnsiblePlayBookEditCatalogForm', AnsiblePlayBookEditCatalogForm);
+ManageIQ.component.addReact('SettingsUsersForm', SettingsUsersForm);
diff --git a/app/javascript/spec/settings-users-form/settings-users-form-spec.js b/app/javascript/spec/settings-users-form/settings-users-form-spec.js
new file mode 100644
index 00000000000..febcb9f4546
--- /dev/null
+++ b/app/javascript/spec/settings-users-form/settings-users-form-spec.js
@@ -0,0 +1,35 @@
+import React from 'react';
+import toJson from 'enzyme-to-json';
+import fetchMock from 'fetch-mock';
+import { act } from 'react-dom/test-utils';
+
+import { mount } from '../helpers/mountForm';
+import SettingsUsersForm from '../../components/settings-users-form';
+
+describe('SettingsUsersForm Component', () => {
+ afterEach(() => {
+ fetchMock.reset();
+ fetchMock.restore();
+ });
+ it('should render a new SettingsUsersForm form', async(done) => {
+ let wrapper;
+ await act(async() => {
+ wrapper = mount();
+ });
+ wrapper.update();
+ expect(toJson(wrapper)).toMatchSnapshot();
+ done();
+ });
+
+ it('should render edit SettingsUsersForm', async(done) => {
+ fetchMock.getOnce('/api/groups?expand=resources/', {});
+ let wrapper;
+ await act(async() => {
+ wrapper = mount();
+ });
+ expect(fetchMock.calls()).toHaveLength(1);
+ expect(fetchMock.called('/api/groups?expand=resources/100')).toBe(true);
+ expect(toJson(wrapper)).toMatchSnapshot();
+ done();
+ });
+});
diff --git a/app/views/ops/_rbac_user_details.html.haml b/app/views/ops/_rbac_user_details.html.haml
index e65c4aa23b3..791c034d93a 100644
--- a/app/views/ops/_rbac_user_details.html.haml
+++ b/app/views/ops/_rbac_user_details.html.haml
@@ -1,4 +1,7 @@
- if @edit
+ = react('SettingsUsersForm', {:recordId => params[:id] || "new"})
+ -# = params[:id]
+ -# = @edit[:user_id]
- change_stored_password ||= _("Change")
- cancel_password_change ||= _("Cancel")
- stored_password_placeholder = "●●●●●●●●"