passbolt-styleguide
Version:
Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.
199 lines (177 loc) • 5.32 kB
JavaScript
/**
* Passbolt ~ Open source password manager for teams
* Copyright (c) 2020 Passbolt SA (https://www.passbolt.com)
*
* Licensed under GNU Affero General Public License version 3 of the or any later version.
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) 2020 Passbolt SA (https://www.passbolt.com)
* @license https://opensource.org/licenses/AGPL-3.0 AGPL License
* @link https://www.passbolt.com Passbolt(tm)
* @since 2.13.0
*/
import React, { Component } from "react";
import PropTypes from "prop-types";
import { withTranslation } from "react-i18next";
import UserAvatarSVG from "../../../../img/avatar/user_default.svg";
import AttentionSVG from "../../../../img/svg/attention.svg";
import { BROWSER_NAMES, detectBrowserName } from "../../../../shared/lib/Browser/detectBrowserName";
const DEFAULT_AVATAR_URL_REGEXP = /img\/avatar\/user(?:_medium)?\.png$/;
class UserAvatar extends Component {
/**
* Constructor
* @param {Object} props
*/
constructor(props) {
super(props);
this.state = this.getDefaultState();
this.bindCallbacks();
}
/**
* Get default state
* @returns {*}
*/
getDefaultState() {
return {
error: false,
isLoading: true,
};
}
/**
* Bind callbacks methods
* @return {void}
*/
bindCallbacks() {
this.handleError = this.handleError.bind(this);
this.handleLoaded = this.handleLoaded.bind(this);
}
/**
* Returns the current avatar URL from the props
* @returns {string}
*/
get avatarUrl() {
return this.props?.user?.profile?.avatar?.url?.medium;
}
/**
* Return true if the user from props contains a valid profile with avatar url properties
* @returns {boolean}
*/
propsHasUrl() {
return Boolean(this.avatarUrl);
}
/**
* Check if the url has a protocol like http or https?
* @todo only check https for now
* @returns {boolean}
*/
propsUrlHasProtocol() {
return this.avatarUrl.startsWith("https://") || this.avatarUrl.startsWith("http://");
}
/**
* Format the avatar url to point on the site url.
* @param {string} url The relative url
* @returns {string}
*/
formatUrl(url) {
return `${this.props.baseUrl}/${url}`;
}
/**
* Returns true if the given URL matches a default avatar from the API
* @returns {boolean}
*/
isDefaultAvatarUrlFromApi() {
return DEFAULT_AVATAR_URL_REGEXP.test(this.avatarUrl);
}
/**
* Returns true if we are currently running under Safari.
* @todo: to be removed when Safari stops breaking user's session when downloading images
* @returns {boolean}
*/
isRunningUnderSafari() {
return detectBrowserName() === BROWSER_NAMES.SAFARI;
}
/**
* Returns true if the default avatar must be displayed instead of a custom avatar.
* @returns {boolean}
*/
shouldDisplayDefaultAvatar() {
const hasAvatarUrl = Boolean(this.getAvatarSrc());
return this.state.error || !this.props.user || this.isDefaultAvatarUrlFromApi() || !hasAvatarUrl;
}
/**
* Get the user avatar url. If the user has no avatar defined, return the default one.
* @returns {string}
*/
getAvatarSrc() {
if (!this.propsHasUrl()) {
return null;
}
return this.propsUrlHasProtocol() ? this.avatarUrl : this.formatUrl(this.avatarUrl);
}
/**
* Handle error while loading the user avatar image.
* By instance when the image is not present on the server.
* @return {void}
*/
handleError() {
console.error(`Could not load avatar image url: ${this.getAvatarSrc()}`);
this.setState({ error: true });
}
/**
* Handle loaded image event.
* @return {void}
*/
handleLoaded() {
this.setState({ isLoading: false });
}
/**
* Get the user avatar image alternative text.
* @returns {string}
*/
getAltText() {
const user = this.props?.user;
if (!user?.first_name || !user?.last_name) {
return "...";
}
return this.props.t("Avatar of user {{first_name}} {{last_name}}.", {
firstname: user.first_name,
lastname: user.last_name,
});
}
/**
* Render the component
* @return {JSX}
*/
render() {
const shouldDisplayDefaultAvatar = this.shouldDisplayDefaultAvatar();
return (
<div className={`${this.props.className}`}>
<div className="default-avatar">
{(shouldDisplayDefaultAvatar || this.state.isLoading) && <UserAvatarSVG />}
{!shouldDisplayDefaultAvatar && (
<img
src={this.getAvatarSrc()}
className={this.state.isLoading ? "is-loading" : ""}
onError={this.handleError}
onLoad={this.handleLoaded}
alt={this.getAltText()}
/>
)}
</div>
{this.props.attentionRequired && <AttentionSVG className="attention-required" />}
</div>
);
}
}
UserAvatar.defaultProps = {
className: "avatar user-avatar",
};
UserAvatar.propTypes = {
baseUrl: PropTypes.string,
user: PropTypes.object,
attentionRequired: PropTypes.bool,
className: PropTypes.string,
t: PropTypes.func,
};
export default withTranslation("common")(UserAvatar);