Skip to content

Commit

Permalink
UIU-3005 - Display Profile picture on edit screen based on user permi…
Browse files Browse the repository at this point in the history
…ssion. (#2620)

* UIU-3005 - edit screen

* UIU-3005 - fix linting configuration

* cleanup

* Revert "UIU-3005 - fix linting configuration"

This reverts commit 322a455.

* 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
  • Loading branch information
Terala-Priyanka authored Feb 7, 2024
1 parent 103a44b commit cba4384
Show file tree
Hide file tree
Showing 20 changed files with 360 additions and 135 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions src/components/EditSections/EditUserInfo/EditUserInfo.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@
top: -10px;
font-size: var(--font-size-small);
}

.profilePlaceholder {
max-width: 100px;
max-height: 100px;
}
274 changes: 152 additions & 122 deletions src/components/EditSections/EditUserInfo/EditUserInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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) {
Expand Down Expand Up @@ -117,6 +120,7 @@ class EditUserInfo extends React.Component {
stripes,
uniquenessValidator,
disabled,
areProfilePicturesEnabled,
} = this.props;

const isConsortium = isConsortiumEnabled(stripes);
Expand Down Expand Up @@ -223,6 +227,8 @@ class EditUserInfo extends React.Component {
</ModalFooter>
);

const hasViewProfilePicturePerm = stripes.hasPerm('ui-users.profile-pictures.view');

return (
<>
<Accordion
Expand All @@ -235,131 +241,154 @@ class EditUserInfo extends React.Component {
{ initialValues.metadata && <ViewMetaData metadata={initialValues.metadata} /> }

<Row>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.lastName" />}
name="personal.lastName"
id="adduser_lastname"
component={TextField}
required
fullWidth
autoFocus
disabled={disabled}
/>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.firstName" />}
name="personal.firstName"
id="adduser_firstname"
component={TextField}
fullWidth
disabled={disabled}
/>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.middleName" />}
name="personal.middleName"
id="adduser_middlename"
component={TextField}
fullWidth
disabled={disabled}
/>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.preferredName" />}
name="personal.preferredFirstName"
id="adduser_preferredname"
component={TextField}
fullWidth
disabled={disabled}
/>
<Col xs={areProfilePicturesEnabled && hasViewProfilePicturePerm ? 9 : 12}>
<Row>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.lastName" />}
name="personal.lastName"
id="adduser_lastname"
component={TextField}
required
fullWidth
autoFocus
disabled={disabled}
/>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.firstName" />}
name="personal.firstName"
id="adduser_firstname"
component={TextField}
fullWidth
disabled={disabled}
/>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.middleName" />}
name="personal.middleName"
id="adduser_middlename"
component={TextField}
fullWidth
disabled={disabled}
/>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.preferredName" />}
name="personal.preferredFirstName"
id="adduser_preferredname"
component={TextField}
fullWidth
disabled={disabled}
/>
</Col>
</Row>

<Row>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.patronGroup" />}
name="patronGroup"
id="adduser_group"
component={Select}
selectClass={css.patronGroup}
fullWidth
dataOptions={patronGroupOptions}
defaultValue={initialValues.patronGroup}
aria-required="true"
required={!disabled}
/>
<OnChange name="patronGroup">
{(selectedPatronGroup) => {
this.setState({ selectedPatronGroup }, () => {
if (this.getPatronGroupOffset()) {
this.showModal(true);
}
});
}}
</OnChange>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.status" />}
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() && (
<span className={css.expiredMessage}>
<FormattedMessage id="ui-users.errors.userExpired" />
</span>
)}
{isUserExpired() && willUserExtend() && (
<p className={css.expiredMessage} id="saving-will-reactivate-user">
<FormattedMessage id="ui-users.information.recalculate.will.reactivate.user" />
</p>
)}
</Col>
<Col xs={12} md={3}>
<Field
component={Datepicker}
label={<FormattedMessage id="ui-users.expirationDate" />}
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() && (
<Button
id="recalculate-expirationDate-btn"
onClick={() => this.setRecalculatedExpirationDate(false)}
>
<FormattedMessage id="ui-users.information.recalculate.expirationDate" />
</Button>
)}
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.barcode" />}
name="barcode"
id="adduser_barcode"
component={TextField}
validate={asyncValidateField('barcode', barcode, uniquenessValidator)}
fullWidth
disabled={disabled}
/>
</Col>
</Row>
</Col>
</Row>

{
areProfilePicturesEnabled && hasViewProfilePicturePerm &&
<Col xs={3}>
<Row>
<Col xs={12}>
<Field
label={<FormattedMessage id="ui-users.information.profilePicture" />}
id="profilePicture"
name="profilePicture"
profilePictureLink={initialValues?.personal?.profilePictureLink}
render={(props) => (<ProfilePicture {...props} />)}
/>
</Col>
</Row>
</Col>
}
</Row>
<Row>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.patronGroup" />}
name="patronGroup"
id="adduser_group"
component={Select}
selectClass={css.patronGroup}
fullWidth
dataOptions={patronGroupOptions}
defaultValue={initialValues.patronGroup}
aria-required="true"
required={!disabled}
/>
<OnChange name="patronGroup">
{(selectedPatronGroup) => {
this.setState({ selectedPatronGroup }, () => {
if (this.getPatronGroupOffset()) {
this.showModal(true);
}
});
}}
</OnChange>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.status" />}
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() && (
<span className={css.expiredMessage}>
<FormattedMessage id="ui-users.errors.userExpired" />
</span>
)}
{isUserExpired() && willUserExtend() && (
<p className={css.expiredMessage} id="saving-will-reactivate-user">
<FormattedMessage id="ui-users.information.recalculate.will.reactivate.user" />
</p>
)}
</Col>
<Col xs={12} md={3}>
<Field
component={Datepicker}
label={<FormattedMessage id="ui-users.expirationDate" />}
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() && (
<Button
id="recalculate-expirationDate-btn"
onClick={() => this.setRecalculatedExpirationDate(false)}
>
<FormattedMessage id="ui-users.information.recalculate.expirationDate" />
</Button>
)}
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.barcode" />}
name="barcode"
id="adduser_barcode"
component={TextField}
validate={asyncValidateField('barcode', barcode, uniquenessValidator)}
fullWidth
disabled={disabled}
/>
</Col>
<Col xs={12} md={3}>
<Field
label={<FormattedMessage id="ui-users.information.userType" />}
Expand All @@ -374,6 +403,7 @@ class EditUserInfo extends React.Component {
/>
</Col>
</Row>

</Accordion>
<Modal
footer={modalFooter}
Expand Down
21 changes: 20 additions & 1 deletion src/components/EditSections/EditUserInfo/EditUserInfo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import EditUserInfo from './EditUserInfo';
import { isConsortiumEnabled } from '../../util';
import { USER_TYPES } from '../../../constants';

jest.mock('../../../hooks', () => ({
useProfilePicture: jest.fn(),
}));
jest.mock('@folio/stripes/components', () => ({
...jest.requireActual('@folio/stripes/components'),
Modal: jest.fn(({ children, label, footer, ...rest }) => {
Expand All @@ -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 = {
Expand Down Expand Up @@ -86,6 +91,7 @@ const props = {
connect: (Component) => Component,
timezone: 'USA/TestTimeZone',
hasInterface: () => true,
hasPerm: () => true,
},
patronGroups: [{
desc: 'Staff Member',
Expand Down Expand Up @@ -121,7 +127,8 @@ const props = {
PUT: jest.fn(),
cancel: jest.fn(),
reset: jest.fn()
}
},
areProfilePicturesEnabled: true,
};

describe('Render Edit User Information component', () => {
Expand Down Expand Up @@ -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();
});
});
});
Loading

0 comments on commit cba4384

Please sign in to comment.