From cba4384dac2d6f1885163d5c3086522881c3260b Mon Sep 17 00:00:00 2001
From: Priyanka Terala <104053200+Terala-Priyanka@users.noreply.github.com>
Date: Wed, 7 Feb 2024 08:30:32 +0530
Subject: [PATCH] UIU-3005 - Display Profile picture on edit screen based on
user permission. (#2620)
* UIU-3005 - edit screen
* UIU-3005 - fix linting configuration
* cleanup
* Revert "UIU-3005 - fix linting configuration"
This reverts commit 322a45533dc0aeca9950430bd902f09ff3fb712b.
* UIU-3005 - cleanup - userdetails - pic useProfilePicture hook from hooks folder
* UIU-3005 - cleanup
* UIU-3005 - refinement
* UIU-3005 - add unit tests
* UIU-3005 - cleanup
* UIU-3005 - i18'ned Update button
* UIU-3005-add more translations
---
CHANGELOG.md | 1 +
.../EditUserInfo/EditUserInfo.css | 5 +
.../EditSections/EditUserInfo/EditUserInfo.js | 274 ++++++++++--------
.../EditUserInfo/EditUserInfo.test.js | 21 +-
.../ProfilePicture/ProfilePicture.js | 92 ++++++
.../ProfilePicture/ProfilePicture.test.js | 58 ++++
.../components/ProfilePicture/index.js | 1 +
.../EditUserInfo/components/index.js | 2 +
.../UserDetailSections/UserInfo/UserInfo.js | 2 +-
.../UserInfo/UserInfo.test.js | 4 +-
.../UserInfo/hooks/index.js | 1 -
src/hooks/index.js | 1 +
.../hooks/useProfilePicture/index.js | 0
.../useProfilePicture/useProfilePicture.js | 6 +-
.../useProfilePicture.test.js | 4 +-
src/views/UserDetail/UserDetail.js | 12 +-
src/views/UserDetail/UserDetail.test.js | 1 +
src/views/UserEdit/UserEdit.js | 3 +
src/views/UserEdit/UserForm.js | 3 +
translations/ui-users/en.json | 4 +
20 files changed, 360 insertions(+), 135 deletions(-)
create mode 100644 src/components/EditSections/EditUserInfo/components/ProfilePicture/ProfilePicture.js
create mode 100644 src/components/EditSections/EditUserInfo/components/ProfilePicture/ProfilePicture.test.js
create mode 100644 src/components/EditSections/EditUserInfo/components/ProfilePicture/index.js
create mode 100644 src/components/EditSections/EditUserInfo/components/index.js
delete mode 100644 src/components/UserDetailSections/UserInfo/hooks/index.js
rename src/{components/UserDetailSections/UserInfo => }/hooks/useProfilePicture/index.js (100%)
rename src/{components/UserDetailSections/UserInfo => }/hooks/useProfilePicture/useProfilePicture.js (83%)
rename src/{components/UserDetailSections/UserInfo => }/hooks/useProfilePicture/useProfilePicture.test.js (93%)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3514b4f24..b96ae37f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@
* Update sub permissions of permission 'Users: Can view user profiles'. Refs UIU-3038.
* Create new permission 'Users: Can view, edit, and delete profile pictures'. Refs UIU-3025.
* UserInformation in UserDetails to display profile picture. Refs UIU-3011.
+* User Information in User Edit to display profile picture and update button set. Refs UIU-3005.
## [10.0.4](https://github.com/folio-org/ui-users/tree/v10.0.4) (2023-11-10)
[Full Changelog](https://github.com/folio-org/ui-users/compare/v10.0.3...v10.0.4)
diff --git a/src/components/EditSections/EditUserInfo/EditUserInfo.css b/src/components/EditSections/EditUserInfo/EditUserInfo.css
index a970323c2..87607a4c3 100644
--- a/src/components/EditSections/EditUserInfo/EditUserInfo.css
+++ b/src/components/EditSections/EditUserInfo/EditUserInfo.css
@@ -33,3 +33,8 @@
top: -10px;
font-size: var(--font-size-small);
}
+
+.profilePlaceholder {
+ max-width: 100px;
+ max-height: 100px;
+}
diff --git a/src/components/EditSections/EditUserInfo/EditUserInfo.js b/src/components/EditSections/EditUserInfo/EditUserInfo.js
index b81c5919d..2aebf9cf8 100644
--- a/src/components/EditSections/EditUserInfo/EditUserInfo.js
+++ b/src/components/EditSections/EditUserInfo/EditUserInfo.js
@@ -26,6 +26,7 @@ import asyncValidateField from '../../validators/asyncValidateField';
import validateMinDate from '../../validators/validateMinDate';
import css from './EditUserInfo.css';
+import ProfilePicture from './components/ProfilePicture';
class EditUserInfo extends React.Component {
static propTypes = {
@@ -41,10 +42,12 @@ class EditUserInfo extends React.Component {
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func,
}),
+ hasPerm: PropTypes.func,
}).isRequired,
form: PropTypes.object,
disabled: PropTypes.bool,
uniquenessValidator: PropTypes.object,
+ areProfilePicturesEnabled: PropTypes.bool.isRequired,
};
constructor(props) {
@@ -117,6 +120,7 @@ class EditUserInfo extends React.Component {
stripes,
uniquenessValidator,
disabled,
+ areProfilePicturesEnabled,
} = this.props;
const isConsortium = isConsortiumEnabled(stripes);
@@ -223,6 +227,8 @@ class EditUserInfo extends React.Component {
);
+ const hasViewProfilePicturePerm = stripes.hasPerm('ui-users.profile-pictures.view');
+
return (
<>
}
-
- }
- name="personal.lastName"
- id="adduser_lastname"
- component={TextField}
- required
- fullWidth
- autoFocus
- disabled={disabled}
- />
-
-
- }
- name="personal.firstName"
- id="adduser_firstname"
- component={TextField}
- fullWidth
- disabled={disabled}
- />
-
-
- }
- name="personal.middleName"
- id="adduser_middlename"
- component={TextField}
- fullWidth
- disabled={disabled}
- />
-
-
- }
- name="personal.preferredFirstName"
- id="adduser_preferredname"
- component={TextField}
- fullWidth
- disabled={disabled}
- />
+
+
+
+ }
+ name="personal.lastName"
+ id="adduser_lastname"
+ component={TextField}
+ required
+ fullWidth
+ autoFocus
+ disabled={disabled}
+ />
+
+
+ }
+ name="personal.firstName"
+ id="adduser_firstname"
+ component={TextField}
+ fullWidth
+ disabled={disabled}
+ />
+
+
+ }
+ name="personal.middleName"
+ id="adduser_middlename"
+ component={TextField}
+ fullWidth
+ disabled={disabled}
+ />
+
+
+ }
+ name="personal.preferredFirstName"
+ id="adduser_preferredname"
+ component={TextField}
+ fullWidth
+ disabled={disabled}
+ />
+
+
+
+
+
+ }
+ name="patronGroup"
+ id="adduser_group"
+ component={Select}
+ selectClass={css.patronGroup}
+ fullWidth
+ dataOptions={patronGroupOptions}
+ defaultValue={initialValues.patronGroup}
+ aria-required="true"
+ required={!disabled}
+ />
+
+ {(selectedPatronGroup) => {
+ this.setState({ selectedPatronGroup }, () => {
+ if (this.getPatronGroupOffset()) {
+ this.showModal(true);
+ }
+ });
+ }}
+
+
+
+ }
+ name="active"
+ id="useractive"
+ component={Select}
+ fullWidth
+ disabled={disabled || isStatusFieldDisabled()}
+ dataOptions={statusOptions}
+ defaultValue={initialValues.active}
+ format={(v) => (v ? v.toString() : 'false')}
+ aria-required="true"
+ required
+ />
+ {isUserExpired() && (
+
+
+
+ )}
+ {isUserExpired() && willUserExtend() && (
+
+
+
+ )}
+
+
+ }
+ dateFormat="YYYY-MM-DD"
+ defaultValue={initialValues.expirationDate}
+ name="expirationDate"
+ id="adduser_expirationdate"
+ parse={this.parseExpirationDate}
+ disabled={disabled}
+ validate={validateMinDate('ui-users.errors.personal.dateOfBirth')}
+ />
+ {checkShowRecalculateButton() && (
+
+ )}
+
+
+ }
+ name="barcode"
+ id="adduser_barcode"
+ component={TextField}
+ validate={asyncValidateField('barcode', barcode, uniquenessValidator)}
+ fullWidth
+ disabled={disabled}
+ />
+
+
-
+ {
+ areProfilePicturesEnabled && hasViewProfilePicturePerm &&
+
+
+
+ }
+ id="profilePicture"
+ name="profilePicture"
+ profilePictureLink={initialValues?.personal?.profilePictureLink}
+ render={(props) => ()}
+ />
+
+
+
+ }
+
-
- }
- name="patronGroup"
- id="adduser_group"
- component={Select}
- selectClass={css.patronGroup}
- fullWidth
- dataOptions={patronGroupOptions}
- defaultValue={initialValues.patronGroup}
- aria-required="true"
- required={!disabled}
- />
-
- {(selectedPatronGroup) => {
- this.setState({ selectedPatronGroup }, () => {
- if (this.getPatronGroupOffset()) {
- this.showModal(true);
- }
- });
- }}
-
-
-
- }
- name="active"
- id="useractive"
- component={Select}
- fullWidth
- disabled={disabled || isStatusFieldDisabled()}
- dataOptions={statusOptions}
- defaultValue={initialValues.active}
- format={(v) => (v ? v.toString() : 'false')}
- aria-required="true"
- required
- />
- {isUserExpired() && (
-
-
-
- )}
- {isUserExpired() && willUserExtend() && (
-
-
-
- )}
-
-
- }
- dateFormat="YYYY-MM-DD"
- defaultValue={initialValues.expirationDate}
- name="expirationDate"
- id="adduser_expirationdate"
- parse={this.parseExpirationDate}
- disabled={disabled}
- validate={validateMinDate('ui-users.errors.personal.dateOfBirth')}
- />
- {checkShowRecalculateButton() && (
-
- )}
-
-
- }
- name="barcode"
- id="adduser_barcode"
- component={TextField}
- validate={asyncValidateField('barcode', barcode, uniquenessValidator)}
- fullWidth
- disabled={disabled}
- />
-
}
@@ -374,6 +403,7 @@ class EditUserInfo extends React.Component {
/>
+
({
+ useProfilePicture: jest.fn(),
+}));
jest.mock('@folio/stripes/components', () => ({
...jest.requireActual('@folio/stripes/components'),
Modal: jest.fn(({ children, label, footer, ...rest }) => {
@@ -34,6 +37,8 @@ jest.mock('../../util', () => ({
isConsortiumEnabled: jest.fn(() => true),
}));
+jest.mock('./components/ProfilePicture', () => jest.fn(() => 'Profile Picture'));
+
const onSubmit = jest.fn();
const arrayMutators = {
@@ -86,6 +91,7 @@ const props = {
connect: (Component) => Component,
timezone: 'USA/TestTimeZone',
hasInterface: () => true,
+ hasPerm: () => true,
},
patronGroups: [{
desc: 'Staff Member',
@@ -121,7 +127,8 @@ const props = {
PUT: jest.fn(),
cancel: jest.fn(),
reset: jest.fn()
- }
+ },
+ areProfilePicturesEnabled: true,
};
describe('Render Edit User Information component', () => {
@@ -182,4 +189,16 @@ describe('Render Edit User Information component', () => {
expect(screen.getByRole('textbox', { name: /lastName/ })).toBeDisabled();
expect(screen.getByRole('textbox', { name: /firstName/ })).toBeDisabled();
});
+
+ it('should display profile picture', () => {
+ renderEditUserInfo(props);
+ expect(screen.getByText('Profile Picture')).toBeInTheDocument();
+ });
+
+ describe('when profilePicture configuration is not enabled', () => {
+ it('should not render profile picture', () => {
+ renderEditUserInfo({ ...props, areProfilePicturesEnabled: false });
+ expect(screen.queryByText('Profile Picture')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/src/components/EditSections/EditUserInfo/components/ProfilePicture/ProfilePicture.js b/src/components/EditSections/EditUserInfo/components/ProfilePicture/ProfilePicture.js
new file mode 100644
index 000000000..00ea90c80
--- /dev/null
+++ b/src/components/EditSections/EditUserInfo/components/ProfilePicture/ProfilePicture.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage, useIntl } from 'react-intl';
+
+import {
+ Button,
+ Dropdown,
+ DropdownMenu,
+ Icon,
+ Label,
+} from '@folio/stripes/components';
+import { useStripes } from '@folio/stripes/core';
+
+import { useProfilePicture } from '../../../../../hooks';
+import { isAValidUUID } from '../../../../util/util';
+import profilePicThumbnail from '../../../../../../icons/profilePicThumbnail.png';
+import css from '../../EditUserInfo.css';
+
+const ProfilePicture = ({ label, profilePictureLink }) => {
+ const intl = useIntl();
+ const stripes = useStripes();
+ const isProfilePictureLinkAURL = !isAValidUUID(profilePictureLink);
+ const hasProfilePicture = Boolean(profilePictureLink);
+ const { isFetching, profilePictureData } = useProfilePicture({ profilePictureId: profilePictureLink });
+ const hasAllProfilePicturePerms = stripes.hasPerm('ui-users.profile-pictures.all');
+
+ const renderProfilePic = () => {
+ const profilePictureSrc = isProfilePictureLinkAURL ? profilePictureLink : 'data:;base64,' + profilePictureData;
+ const imgSrc = isFetching || !hasProfilePicture ? profilePicThumbnail : profilePictureSrc;
+
+ return (
+
+ );
+ };
+
+ const renderMenu = () => (
+
+
+
+
+
+ );
+
+ return (
+ <>
+
+ { renderProfilePic()}
+
+ {
+ hasAllProfilePicturePerms && (
+ }
+ placement="bottom-end"
+ renderMenu={renderMenu}
+ />
+ )
+ }
+ >
+ );
+};
+
+ProfilePicture.propTypes = {
+ label: PropTypes.node.isRequired,
+ profilePictureLink: PropTypes.string,
+};
+
+export default ProfilePicture;
diff --git a/src/components/EditSections/EditUserInfo/components/ProfilePicture/ProfilePicture.test.js b/src/components/EditSections/EditUserInfo/components/ProfilePicture/ProfilePicture.test.js
new file mode 100644
index 000000000..1e709c263
--- /dev/null
+++ b/src/components/EditSections/EditUserInfo/components/ProfilePicture/ProfilePicture.test.js
@@ -0,0 +1,58 @@
+import { render, screen } from '@folio/jest-config-stripes/testing-library/react';
+import userEvent from '@folio/jest-config-stripes/testing-library/user-event';
+import profilePicData from 'fixtures/profilePicture';
+
+import ProfilePicture from './ProfilePicture';
+import { useProfilePicture } from '../../../../../hooks';
+
+jest.unmock('@folio/stripes/components');
+
+jest.mock('../../../../../hooks', () => ({
+ useProfilePicture: jest.fn(),
+}));
+
+const props = {
+ label: 'Profile picture',
+ profilePictureLink: 'profilePictureLink'
+};
+
+describe('Profile Picture', () => {
+ beforeEach(() => {
+ useProfilePicture.mockClear().mockReturnValue(profilePicData.profile_picture_blob);
+ render();
+ });
+
+ it('should display Profile picture', () => {
+ expect(screen.getByTestId('profile-picture')).toBeInTheDocument();
+ });
+
+ it('Image to be displayed with correct src', () => {
+ const image = screen.getByTestId('profile-picture');
+ expect(image.src).toContain('profilePictureLink');
+ });
+
+ it('Update button to be displayed', () => {
+ expect(screen.getByTestId('updateProfilePictureDropdown')).toBeInTheDocument();
+ });
+
+ it('Local file button to be displayed', async () => {
+ const updateButton = screen.getByTestId('updateProfilePictureDropdown');
+ await userEvent.click(updateButton);
+
+ expect(screen.getByText('Icon (profile)')).toBeInTheDocument();
+ });
+
+ it('External link button to be displayed', async () => {
+ const updateButton = screen.getByTestId('updateProfilePictureDropdown');
+ await userEvent.click(updateButton);
+
+ expect(screen.getByText('Icon (external-link)')).toBeInTheDocument();
+ });
+
+ it('Delete link button to be displayed', async () => {
+ const updateButton = screen.getByTestId('updateProfilePictureDropdown');
+ await userEvent.click(updateButton);
+
+ expect(screen.getByText('Icon (trash)')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/EditSections/EditUserInfo/components/ProfilePicture/index.js b/src/components/EditSections/EditUserInfo/components/ProfilePicture/index.js
new file mode 100644
index 000000000..ffff0ba8c
--- /dev/null
+++ b/src/components/EditSections/EditUserInfo/components/ProfilePicture/index.js
@@ -0,0 +1 @@
+export { default } from './ProfilePicture';
diff --git a/src/components/EditSections/EditUserInfo/components/index.js b/src/components/EditSections/EditUserInfo/components/index.js
new file mode 100644
index 000000000..2497a0f13
--- /dev/null
+++ b/src/components/EditSections/EditUserInfo/components/index.js
@@ -0,0 +1,2 @@
+/* eslint-disable import/prefer-default-export */
+export { default as ProfilePicture } from './ProfilePicture';
diff --git a/src/components/UserDetailSections/UserInfo/UserInfo.js b/src/components/UserDetailSections/UserInfo/UserInfo.js
index f0d77a594..1ef82fbe6 100644
--- a/src/components/UserDetailSections/UserInfo/UserInfo.js
+++ b/src/components/UserDetailSections/UserInfo/UserInfo.js
@@ -19,7 +19,7 @@ import { USER_TYPE_FIELD } from '../../../constants';
import profilePicThumbnail from '../../../../icons/profilePicThumbnail.png';
import { isAValidUUID } from '../../util/util';
-import { useProfilePicture } from './hooks';
+import { useProfilePicture } from '../../../hooks';
const UserInfo = (props) => {
const {
diff --git a/src/components/UserDetailSections/UserInfo/UserInfo.test.js b/src/components/UserDetailSections/UserInfo/UserInfo.test.js
index ce992f91c..befd59af2 100644
--- a/src/components/UserDetailSections/UserInfo/UserInfo.test.js
+++ b/src/components/UserDetailSections/UserInfo/UserInfo.test.js
@@ -2,13 +2,13 @@ import { screen } from '@folio/jest-config-stripes/testing-library/react';
import renderWithRouter from 'helpers/renderWithRouter';
import UserInfo from './UserInfo';
-import { useProfilePicture } from './hooks';
+import { useProfilePicture } from '../../../hooks';
import profilePicData from '../../../../test/jest/fixtures/profilePicture';
const toggleMock = jest.fn();
-jest.mock('./hooks', () => ({
+jest.mock('../../../hooks', () => ({
useProfilePicture: jest.fn(),
}));
diff --git a/src/components/UserDetailSections/UserInfo/hooks/index.js b/src/components/UserDetailSections/UserInfo/hooks/index.js
deleted file mode 100644
index e2cdaa9b8..000000000
--- a/src/components/UserDetailSections/UserInfo/hooks/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default as useProfilePicture } from './useProfilePicture';
diff --git a/src/hooks/index.js b/src/hooks/index.js
index 317157751..a2348027d 100644
--- a/src/hooks/index.js
+++ b/src/hooks/index.js
@@ -5,3 +5,4 @@ export { default as useToggle } from './useToggle';
export { default as useUserAffiliations } from './useUserAffiliations';
export { default as useUserAffiliationsMutation } from './useUserAffiliationsMutation';
export { default as useUserTenantPermissions } from './useUserTenantPermissions';
+export { default as useProfilePicture } from './useProfilePicture';
diff --git a/src/components/UserDetailSections/UserInfo/hooks/useProfilePicture/index.js b/src/hooks/useProfilePicture/index.js
similarity index 100%
rename from src/components/UserDetailSections/UserInfo/hooks/useProfilePicture/index.js
rename to src/hooks/useProfilePicture/index.js
diff --git a/src/components/UserDetailSections/UserInfo/hooks/useProfilePicture/useProfilePicture.js b/src/hooks/useProfilePicture/useProfilePicture.js
similarity index 83%
rename from src/components/UserDetailSections/UserInfo/hooks/useProfilePicture/useProfilePicture.js
rename to src/hooks/useProfilePicture/useProfilePicture.js
index 588b01d94..5cb7a0b23 100644
--- a/src/components/UserDetailSections/UserInfo/hooks/useProfilePicture/useProfilePicture.js
+++ b/src/hooks/useProfilePicture/useProfilePicture.js
@@ -5,14 +5,15 @@ import {
useOkapiKy,
} from '@folio/stripes/core';
-import { PROFILE_PIC_API } from '../../../../../constants';
-import { isAValidUUID } from '../../../../util/util';
+import { PROFILE_PIC_API } from '../../constants';
+import { isAValidUUID } from '../../components/util/util';
const useProfilePicture = ({ profilePictureId }, options = {}) => {
const ky = useOkapiKy();
const [namespace] = useNamespace({ key: 'get-profile-picture-of-a-user' });
const {
isFetching,
+ isLoading,
data = {},
} = useQuery(
[namespace, profilePictureId],
@@ -26,6 +27,7 @@ const useProfilePicture = ({ profilePictureId }, options = {}) => {
);
return ({
+ isLoading,
isFetching,
profilePictureData: data?.profile_picture_blob,
});
diff --git a/src/components/UserDetailSections/UserInfo/hooks/useProfilePicture/useProfilePicture.test.js b/src/hooks/useProfilePicture/useProfilePicture.test.js
similarity index 93%
rename from src/components/UserDetailSections/UserInfo/hooks/useProfilePicture/useProfilePicture.test.js
rename to src/hooks/useProfilePicture/useProfilePicture.test.js
index ac29960dc..22309ca9f 100644
--- a/src/components/UserDetailSections/UserInfo/hooks/useProfilePicture/useProfilePicture.test.js
+++ b/src/hooks/useProfilePicture/useProfilePicture.test.js
@@ -29,10 +29,11 @@ describe('useProfilePicture', () => {
profilePictureId: undefined,
}), { wrapper });
- await waitFor(() => !result.current.isFetching);
+ await waitFor(() => !result.current.isLoading);
expect(result.current).toEqual(expect.objectContaining({
'isFetching': false,
+ 'isLoading': false,
'profilePictureData': undefined,
}));
});
@@ -46,6 +47,7 @@ describe('useProfilePicture', () => {
expect(result.current).toEqual(expect.objectContaining({
'isFetching': false,
+ 'isLoading': false,
'profilePictureData': profilePicture.profile_picture_blob
}));
});
diff --git a/src/views/UserDetail/UserDetail.js b/src/views/UserDetail/UserDetail.js
index 86885bcbf..259fa9cd3 100644
--- a/src/views/UserDetail/UserDetail.js
+++ b/src/views/UserDetail/UserDetail.js
@@ -424,6 +424,7 @@ class UserDetail extends React.Component {
},
},
resources,
+ stripes,
} = this.props;
const user = this.getUser();
const patronGroup = this.getPatronGroup(user);
@@ -442,11 +443,12 @@ class UserDetail extends React.Component {
loans,
};
- const showActionMenu = this.props.stripes.hasPerm('ui-users.edit')
- || this.props.stripes.hasPerm('ui-users.patron_blocks')
- || this.props.stripes.hasPerm('ui-users.feesfines.actions.all')
- || this.props.stripes.hasPerm('ui-requests.all')
- || this.props.stripes.hasPerm('ui-users.delete,ui-users.opentransactions');
+ const showActionMenu = stripes.hasPerm('ui-users.edit')
+ || stripes.hasPerm('ui-users.patron_blocks')
+ || stripes.hasPerm('ui-users.feesfines.actions.all')
+ || stripes.hasPerm('ui-requests.all')
+ || stripes.hasPerm('ui-users.delete,ui-users.opentransactions')
+ || stripes.hasPerm('ui-users.profile-pictures.all');
if (showActionMenu && !isVirtualPatron) {
return (
diff --git a/src/views/UserDetail/UserDetail.test.js b/src/views/UserDetail/UserDetail.test.js
index 5a5d46c24..92de85592 100644
--- a/src/views/UserDetail/UserDetail.test.js
+++ b/src/views/UserDetail/UserDetail.test.js
@@ -198,6 +198,7 @@ describe('UserDetail', () => {
beforeEach(() => {
stripes = useStripes();
+ stripes.hasPerm = () => true;
mutator.hasManualPatronBlocks.GET.mockImplementation(() => Promise.resolve([]));
mutator.hasAutomatedPatronBlocks.GET.mockImplementation(() => Promise.resolve([]));
});
diff --git a/src/views/UserEdit/UserEdit.js b/src/views/UserEdit/UserEdit.js
index 6698ff9c4..5db197074 100644
--- a/src/views/UserEdit/UserEdit.js
+++ b/src/views/UserEdit/UserEdit.js
@@ -377,6 +377,8 @@ class UserEdit extends React.Component {
match: { params },
} = this.props;
+ const areProfilePicturesEnabled = get(resources, 'settings.records[0].enabled');
+
if (!resourcesLoaded(resources, ['uniquenessValidator']) || (!this.getUser() && this.props.match.params.id)) {
return (
);
}
diff --git a/src/views/UserEdit/UserForm.js b/src/views/UserEdit/UserForm.js
index eb16cf854..de9d1ed62 100644
--- a/src/views/UserEdit/UserForm.js
+++ b/src/views/UserEdit/UserForm.js
@@ -103,6 +103,7 @@ class UserForm extends React.Component {
stripes: PropTypes.object,
form: PropTypes.object, // provided by final-form
intl: PropTypes.object,
+ areProfilePicturesEnabled: PropTypes.bool.isRequired,
};
static defaultProps = {
@@ -295,6 +296,7 @@ class UserForm extends React.Component {
stripes,
form,
uniquenessValidator,
+ areProfilePicturesEnabled,
} = this.props;
const selectedPatronGroup = form.getFieldState('patronGroup')?.value;
@@ -364,6 +366,7 @@ class UserForm extends React.Component {
selectedPatronGroup={selectedPatronGroup}
uniquenessValidator={uniquenessValidator}
disabled={isShadowUser}
+ areProfilePicturesEnabled={areProfilePicturesEnabled}
/>