Skip to content

Commit

Permalink
UIU-3011 - UserInformation in UserDetails to display profile picture.
Browse files Browse the repository at this point in the history
  • Loading branch information
Terala-Priyanka committed Jan 25, 2024
1 parent 6ed24d8 commit 447c2fa
Show file tree
Hide file tree
Showing 13 changed files with 251 additions and 121 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
* Create new permission 'Users: Can view profile pictures'. Refs UIU-3018.
* Format currency values as currencies, not numbers. Refs UIU-2026.
* Show country name in user address instead of country id. Refs UIU-2976.
* UserInformation in UserDetails to display profile picture. Refs UIU-3011.

## [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
Binary file added icons/ProfilePicThumbnail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/components/UserDetailSections/UserInfo/UserInfo.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@
.profilePlaceholder {
width: 100px;
height: 100px;
object-fit: scale-down;
}
253 changes: 134 additions & 119 deletions src/components/UserDetailSections/UserInfo/UserInfo.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { get } from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, useIntl } from 'react-intl';
import {
Row,
Col,
Expand All @@ -13,137 +13,152 @@ import {
} from '@folio/stripes/components';

import { ViewMetaData } from '@folio/stripes/smart-components';
import { useStripes } from '@folio/stripes/core';
import css from './UserInfo.css';
import appIcon from '../../../../icons/app.png';
import { USER_TYPE_FIELD } from '../../../constants';
import ProfilePicThumbnail from '../../../../icons/ProfilePicThumbnail.png';

class UserInfo extends React.Component {
static propTypes = {
expanded: PropTypes.bool,
stripes: PropTypes.object.isRequired,
onToggle: PropTypes.func,
accordionId: PropTypes.string.isRequired,
user: PropTypes.object.isRequired,
patronGroup: PropTypes.object.isRequired,
settings: PropTypes.arrayOf(PropTypes.object).isRequired,
};

constructor(props) {
super(props);
import { useProfilePicture } from './hooks';

this.cViewMetaData = props.stripes.connect(ViewMetaData);
}

render() {
const {
user,
patronGroup,
settings,
expanded,
accordionId,
onToggle,
} = this.props;
const userStatus = (user?.active ?
<FormattedMessage id="ui-users.active" /> :
<FormattedMessage id="ui-users.inactive" />);
const hasProfilePicture = (settings.length && settings[0].value === 'true');
const UserInfo = (props) => {
const {
user,
patronGroup,
settings,
expanded,
accordionId,
onToggle
} = props;
const stripes = useStripes();
const intl = useIntl();
const userStatus = (user?.active ?
<FormattedMessage id="ui-users.active" /> :
<FormattedMessage id="ui-users.inactive" />);
const hasProfilePicture = Boolean(user?.personal?.profilePictureLink);
const profilePicturesEnabled = Boolean(settings.length) && settings[0].enabled;
const hasViewProfilePicPerm = stripes.hasPerm('ui-users.profilepictures.view');
const { isFetching, isLoading, profilePictureData } = useProfilePicture({ profilePictureId: user?.personal?.profilePictureLink });

const renderProfilePic = () => {
const imgSrc = isLoading || isFetching || !hasProfilePicture ? ProfilePicThumbnail : 'data:;base64,' + profilePictureData;
return (
<Accordion
open={expanded}
id={accordionId}
onToggle={onToggle}
label={(
<Headline
size="large"
tag="h3"
>
<FormattedMessage id="ui-users.information.userInformation" />
</Headline>)}
>
<Row>
<Col xs={12}>
<this.cViewMetaData metadata={user.metadata} />
</Col>
</Row>
<Row>
<Col xs={hasProfilePicture ? 9 : 12}>
<Row>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.lastName" />}
value={get(user, ['personal', 'lastName'], '')}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.firstName" />}
value={get(user, ['personal', 'firstName'], '')}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.middleName" />}
value={get(user, ['personal', 'middleName'], '')}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.preferredName" />}
value={get(user, ['personal', 'preferredFirstName']) || <NoValue />}
/>
</Col>
</Row>
<img
className={css.profilePlaceholder}
alt={intl.formatMessage({ id: 'ui-users.information.profilePicture' })}
src={imgSrc}
/>
);
};

return (
<Accordion
open={expanded}
id={accordionId}
onToggle={onToggle}
label={(
<Headline
size="large"
tag="h3"
>
<FormattedMessage id="ui-users.information.userInformation" />
</Headline>)}
>
<Row>
<Col xs={12}>
<ViewMetaData metadata={user?.metadata} />
</Col>
</Row>
<Row>
<Col xs={profilePicturesEnabled && hasViewProfilePicPerm ? 9 : 12}>
<Row>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.lastName" />}
value={get(user, ['personal', 'lastName'], '')}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.firstName" />}
value={get(user, ['personal', 'firstName'], '')}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.middleName" />}
value={get(user, ['personal', 'middleName'], '')}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.preferredName" />}
value={get(user, ['personal', 'preferredFirstName']) || <NoValue />}
/>
</Col>
</Row>

<Row>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.patronGroup" />}
value={patronGroup.group}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.status" />}
value={userStatus}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.expirationDate" />}
value={user.expirationDate ? <FormattedDate value={user.expirationDate} /> : '-'}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.barcode" />}
value={get(user, ['barcode'], '')}
/>
</Col>
</Row>
<Row>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.userType" />}
value={get(user, [USER_TYPE_FIELD], '')}
/>
</Col>
</Row>
</Col>
<Row>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.patronGroup" />}
value={patronGroup.group}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.status" />}
value={userStatus}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.expirationDate" />}
value={user.expirationDate ? <FormattedDate value={user.expirationDate} /> : '-'}
/>
</Col>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.barcode" />}
value={get(user, ['barcode'], '')}
/>
</Col>
</Row>
<Row>
<Col xs={3}>
<KeyValue
label={<FormattedMessage id="ui-users.information.userType" />}
value={get(user, [USER_TYPE_FIELD], '')}
/>
</Col>
</Row>
</Col>

{hasProfilePicture === true &&
{
profilePicturesEnabled &&
hasViewProfilePicPerm &&
<Col xs={3}>
<Row>
<Col xs={12}>
<img className={`floatEnd ${css.profilePlaceholder}`} src={appIcon} alt="presentation" />
<KeyValue
label={<FormattedMessage id="ui-users.information.profilePicture" />}
value={renderProfilePic()}
/>
</Col>
</Row>
</Col>
}
</Row>
</Accordion>
);
}
}
}
</Row>
</Accordion>
);
};

UserInfo.propTypes = {
expanded: PropTypes.bool,
onToggle: PropTypes.func,
accordionId: PropTypes.string.isRequired,
user: PropTypes.object.isRequired,
patronGroup: PropTypes.object.isRequired,
settings: PropTypes.arrayOf(PropTypes.object).isRequired,
};

export default UserInfo;
10 changes: 10 additions & 0 deletions src/components/UserDetailSections/UserInfo/UserInfo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import '__mock__/stripesComponents.mock';

import renderWithRouter from 'helpers/renderWithRouter';
import UserInfo from './UserInfo';
import { useProfilePicture } from './hooks';

import profilePicData from '../../../../test/jest/fixtures/profilePicture';

const toggleMock = jest.fn();

jest.mock('./hooks', () => ({
useProfilePicture: jest.fn(),
}));

const renderUserInfo = (props) => renderWithRouter(<UserInfo {...props} />);

const props = {
Expand Down Expand Up @@ -38,6 +45,9 @@ const props = {
};

describe('Render userInfo component', () => {
beforeEach(() => {
useProfilePicture.mockClear().mockReturnValue(profilePicData.profile_picture_blob);
});
describe('Check if user data are shown', () => {
it('Active Users', () => {
renderUserInfo(props);
Expand Down
1 change: 1 addition & 0 deletions src/components/UserDetailSections/UserInfo/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as useProfilePicture } from './useProfilePicture';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './useProfilePicture';
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useQuery } from 'react-query';

import {
useNamespace,
useOkapiKy,
} from '@folio/stripes/core';

import { PROFILE_PIC_API } from '../../../../../constants';

const useProfilePicture = ({ profilePictureId }, options = {}) => {
const ky = useOkapiKy();
const [namespace] = useNamespace({ key: 'get-profile-picture-of-a-user' });
const DEFAULT_DATA = {};
const {
isFetching,
isLoading,
data = DEFAULT_DATA,
} = useQuery(
[namespace, profilePictureId],
() => {
return ky.get(`${PROFILE_PIC_API}/${profilePictureId}`).json();
},
{
enabled: Boolean(profilePictureId),
...options,
}
);

return ({
isLoading,
isFetching,
profilePictureData: data?.profile_picture_blob,
});
};
export default useProfilePicture;
Loading

0 comments on commit 447c2fa

Please sign in to comment.