about summary refs log tree commit diff
path: root/app/javascript/mastodon/features/account
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2019-03-26 00:36:25 +0100
committerGitHub <noreply@github.com>2019-03-26 00:36:25 +0100
commita96181f16f4ef74ce6a1efc5e893ddd87a127949 (patch)
tree336f9b9b8bbae78f22560f0a17813e2f527c3ebf /app/javascript/mastodon/features/account
parentac0cc692f527959fcf332edb79739c76c748b1d5 (diff)
Redesign profile column in web UI to match design on public pages (#10337)
* Redesign profile column in web UI to match design on public pages

* Make the tab links text bolder
Diffstat (limited to 'app/javascript/mastodon/features/account')
-rw-r--r--app/javascript/mastodon/features/account/components/action_bar.js190
-rw-r--r--app/javascript/mastodon/features/account/components/header.js280
2 files changed, 179 insertions, 291 deletions
diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js
deleted file mode 100644
index 8ed4c917a..000000000
--- a/app/javascript/mastodon/features/account/components/action_bar.js
+++ /dev/null
@@ -1,190 +0,0 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
-import { NavLink } from 'react-router-dom';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { me, isStaff  } from '../../../initial_state';
-import { shortNumberFormat } from '../../../utils/numbers';
-
-const messages = defineMessages({
-  mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
-  direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
-  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
-  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
-  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
-  block: { id: 'account.block', defaultMessage: 'Block @{name}' },
-  mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
-  follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  report: { id: 'account.report', defaultMessage: 'Report @{name}' },
-  share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
-  media: { id: 'account.media', defaultMessage: 'Media' },
-  blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
-  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
-  hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
-  showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
-  pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
-  preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
-  follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
-  favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
-  lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
-  blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
-  domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
-  mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
-  endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
-  unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
-  add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
-  admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
-});
-
-export default @injectIntl
-class ActionBar extends React.PureComponent {
-
-  static propTypes = {
-    account: ImmutablePropTypes.map.isRequired,
-    onFollow: PropTypes.func,
-    onBlock: PropTypes.func.isRequired,
-    onMention: PropTypes.func.isRequired,
-    onDirect: PropTypes.func.isRequired,
-    onReblogToggle: PropTypes.func.isRequired,
-    onReport: PropTypes.func.isRequired,
-    onMute: PropTypes.func.isRequired,
-    onBlockDomain: PropTypes.func.isRequired,
-    onUnblockDomain: PropTypes.func.isRequired,
-    onEndorseToggle: PropTypes.func.isRequired,
-    onAddToList: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  handleShare = () => {
-    navigator.share({
-      url: this.props.account.get('url'),
-    });
-  }
-
-  isStatusesPageActive = (match, location) => {
-    if (!match) {
-      return false;
-    }
-    return !location.pathname.match(/\/(followers|following)\/?$/);
-  }
-
-  render () {
-    const { account, intl } = this.props;
-
-    let menu = [];
-    let extraInfo = '';
-
-    if (account.get('id') !== me) {
-      menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
-      menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
-      menu.push(null);
-    }
-
-    if ('share' in navigator) {
-      menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
-      menu.push(null);
-    }
-
-    if (account.get('id') === me) {
-      menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
-      menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
-      menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
-      menu.push(null);
-      menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
-      menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
-      menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
-      menu.push(null);
-      menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
-      menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
-      menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
-    } else {
-      if (account.getIn(['relationship', 'following'])) {
-        if (account.getIn(['relationship', 'showing_reblogs'])) {
-          menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
-        } else {
-          menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
-        }
-
-        menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
-        menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
-        menu.push(null);
-      }
-
-      if (account.getIn(['relationship', 'muting'])) {
-        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
-      } else {
-        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute });
-      }
-
-      if (account.getIn(['relationship', 'blocking'])) {
-        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
-      } else {
-        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
-      }
-
-      menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
-    }
-
-    if (account.get('acct') !== account.get('username')) {
-      const domain = account.get('acct').split('@')[1];
-
-      extraInfo = (
-        <div className='account__disclaimer'>
-          <FormattedMessage
-            id='account.disclaimer_full'
-            defaultMessage="Information below may reflect the user's profile incompletely."
-          />
-          {' '}
-          <a target='_blank' rel='noopener' href={account.get('url')}>
-            <FormattedMessage id='account.view_full_profile' defaultMessage='View full profile' />
-          </a>
-        </div>
-      );
-
-      menu.push(null);
-
-      if (account.getIn(['relationship', 'domain_blocking'])) {
-        menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
-      } else {
-        menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
-      }
-    }
-
-    if (account.get('id') !== me && isStaff) {
-      menu.push(null);
-      menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
-    }
-
-    return (
-      <div>
-        {extraInfo}
-
-        <div className='account__action-bar'>
-          <div className='account__action-bar-links'>
-            <NavLink isActive={this.isStatusesPageActive} activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}>
-              <FormattedMessage id='account.posts' defaultMessage='Toots' />
-              <strong>{shortNumberFormat(account.get('statuses_count'))}</strong>
-            </NavLink>
-
-            <NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}>
-              <FormattedMessage id='account.follows' defaultMessage='Follows' />
-              <strong>{shortNumberFormat(account.get('following_count'))}</strong>
-            </NavLink>
-
-            <NavLink exact activeClassName='active' className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
-              <FormattedMessage id='account.followers' defaultMessage='Followers' />
-              <strong>{shortNumberFormat(account.get('followers_count'))}</strong>
-            </NavLink>
-          </div>
-
-          <div className='account__action-bar-dropdown'>
-            <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 7fe6d6a4f..41e394585 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -2,13 +2,15 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-import Motion from '../../ui/util/optional_motion';
-import spring from 'react-motion/lib/spring';
+import Button from 'mastodon/components/button';
 import ImmutablePureComponent from 'react-immutable-pure-component';
-import { autoPlayGif, me } from '../../../initial_state';
+import { autoPlayGif, me, isStaff } from 'mastodon/initial_state';
 import classNames from 'classnames';
 import Icon from 'mastodon/components/icon';
+import Avatar from 'mastodon/components/avatar';
+import { shortNumberFormat } from 'mastodon/utils/numbers';
+import { NavLink } from 'react-router-dom';
+import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
 
 const messages = defineMessages({
   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@@ -18,6 +20,32 @@ const messages = defineMessages({
   edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
   linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
   account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
+  mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
+  direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
+  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
+  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+  block: { id: 'account.block', defaultMessage: 'Block @{name}' },
+  mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
+  report: { id: 'account.report', defaultMessage: 'Report @{name}' },
+  share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
+  media: { id: 'account.media', defaultMessage: 'Media' },
+  blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
+  unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
+  hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
+  showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
+  pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
+  preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+  follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
+  favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
+  lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
+  blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
+  domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
+  mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
+  endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
+  unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
+  add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
+  admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
 });
 
 const dateFormatOptions = {
@@ -29,54 +57,6 @@ const dateFormatOptions = {
   minute: '2-digit',
 };
 
-class Avatar extends ImmutablePureComponent {
-
-  static propTypes = {
-    account: ImmutablePropTypes.map.isRequired,
-  };
-
-  state = {
-    isHovered: false,
-  };
-
-  handleMouseOver = () => {
-    if (this.state.isHovered) return;
-    this.setState({ isHovered: true });
-  }
-
-  handleMouseOut = () => {
-    if (!this.state.isHovered) return;
-    this.setState({ isHovered: false });
-  }
-
-  render () {
-    const { account }   = this.props;
-    const { isHovered } = this.state;
-
-    return (
-      <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
-        {({ radius }) => (
-          <a
-            href={account.get('url')}
-            className='account__header__avatar'
-            role='presentation'
-            target='_blank'
-            rel='noopener'
-            style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
-            onMouseOver={this.handleMouseOver}
-            onMouseOut={this.handleMouseOut}
-            onFocus={this.handleMouseOver}
-            onBlur={this.handleMouseOut}
-          >
-            <span style={{ display: 'none' }}>{account.get('acct')}</span>
-          </a>
-        )}
-      </Motion>
-    );
-  }
-
-}
-
 export default @injectIntl
 class Header extends ImmutablePureComponent {
 
@@ -85,64 +65,57 @@ class Header extends ImmutablePureComponent {
     onFollow: PropTypes.func.isRequired,
     onBlock: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
+    domain: PropTypes.string.isRequired,
   };
 
   openEditProfile = () => {
     window.open('/settings/profile', '_blank');
   }
 
+  isStatusesPageActive = (match, location) => {
+    if (!match) {
+      return false;
+    }
+
+    return !location.pathname.match(/\/(followers|following)\/?$/);
+  }
+
   render () {
-    const { account, intl } = this.props;
+    const { account, intl, domain } = this.props;
 
     if (!account) {
       return null;
     }
 
-    let info        = '';
-    let mutingInfo  = '';
+    let info        = [];
     let actionBtn   = '';
     let lockedIcon  = '';
+    let menu        = [];
 
     if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
-      info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
+      info.push(<span className='relationship-tag'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>);
     } else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
-      info = <span className='account--follows-info'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>;
+      info.push(<span className='relationship-tag'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>);
     }
 
     if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
-      mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>;
+      info.push(<span className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
     } else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
-      mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>;
+      info.push(<span className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>);
     }
 
     if (me !== account.get('id')) {
       if (!account.get('relationship')) { // Wait until the relationship is loaded
         actionBtn = '';
       } else if (account.getIn(['relationship', 'requested'])) {
-        actionBtn = (
-          <div className='account--action-button'>
-            <IconButton size={26} active icon='hourglass' title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />
-          </div>
-        );
+        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
       } else if (!account.getIn(['relationship', 'blocking'])) {
-        actionBtn = (
-          <div className='account--action-button'>
-            <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
-          </div>
-        );
+        actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
       } else if (account.getIn(['relationship', 'blocking'])) {
-        actionBtn = (
-          <div className='account--action-button'>
-            <IconButton size={26} icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
-          </div>
-        );
+        actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
       }
     } else {
-      actionBtn = (
-        <div className='account--action-button'>
-          <IconButton size={26} icon='pencil' title={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />
-        </div>
-      );
+      actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.openEditProfile} />;
     }
 
     if (account.get('moved') && !account.getIn(['relationship', 'following'])) {
@@ -153,40 +126,145 @@ class Header extends ImmutablePureComponent {
       lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
     }
 
+    if (account.get('id') !== me) {
+      menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
+      menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
+      menu.push(null);
+    }
+
+    if ('share' in navigator) {
+      menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
+      menu.push(null);
+    }
+
+    if (account.get('id') === me) {
+      menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
+      menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
+      menu.push({ text: intl.formatMessage(messages.pins), to: '/pinned' });
+      menu.push(null);
+      menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
+      menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
+      menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
+      menu.push(null);
+      menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
+      menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
+      menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
+    } else {
+      if (account.getIn(['relationship', 'following'])) {
+        if (account.getIn(['relationship', 'showing_reblogs'])) {
+          menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
+        } else {
+          menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
+        }
+
+        menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
+        menu.push({ text: intl.formatMessage(messages.add_or_remove_from_list), action: this.props.onAddToList });
+        menu.push(null);
+      }
+
+      if (account.getIn(['relationship', 'muting'])) {
+        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute });
+      }
+
+      if (account.getIn(['relationship', 'blocking'])) {
+        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
+      }
+
+      menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
+    }
+
+    if (account.get('acct') !== account.get('username')) {
+      const domain = account.get('acct').split('@')[1];
+
+      menu.push(null);
+
+      if (account.getIn(['relationship', 'domain_blocking'])) {
+        menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
+      } else {
+        menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
+      }
+    }
+
+    if (account.get('id') !== me && isStaff) {
+      menu.push(null);
+      menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${account.get('id')}` });
+    }
+
     const content         = { __html: account.get('note_emojified') };
     const displayNameHtml = { __html: account.get('display_name_html') };
     const fields          = account.get('fields');
-    const badge           = account.get('bot') ? (<div className='roles'><div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div></div>) : null;
+    const badge           = account.get('bot') ? (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>) : null;
+    const acct            = account.get('acct').indexOf('@') === -1 && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
 
     return (
-      <div className={classNames('account__header', { inactive: !!account.get('moved') })} style={{ backgroundImage: `url(${autoPlayGif ? account.get('header') : account.get('header_static')})` }}>
-        <div>
-          <Avatar account={account} />
+      <div className={classNames('account__header', { inactive: !!account.get('moved') })}>
+        <div className='account__header__image'>
+          <div className='account__header__info'>
+            {info}
+          </div>
 
-          <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
-          <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
+          <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />
+        </div>
 
-          {badge}
+        <div className='account__header__bar'>
+          <div className='account__header__tabs'>
+            <a className='avatar' href={account.get('url')}>
+              <Avatar account={account} size={90} />
+            </a>
 
-          <div className='account__header__content' dangerouslySetInnerHTML={content} />
+            <div className='spacer' />
 
-          {fields.size > 0 && (
-            <div className='account__header__fields'>
-              {fields.map((pair, i) => (
-                <dl key={i}>
-                  <dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
+            <div className='account__header__tabs__buttons'>
+              <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
 
-                  <dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
-                    {pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
-                  </dd>
-                </dl>
-              ))}
+              {actionBtn}
             </div>
-          )}
+          </div>
+
+          <div className='account__header__tabs__name'>
+            <h1>
+              <span dangerouslySetInnerHTML={displayNameHtml} /> {badge}
+              <small>@{acct} {lockedIcon}</small>
+            </h1>
+          </div>
+
+          <div className='account__header__extra'>
+            <div className='account__header__bio'>
+              {fields.size > 0 && (
+                <div className='account__header__fields'>
+                  {fields.map((pair, i) => (
+                    <dl key={i}>
+                      <dt dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} title={pair.get('name')} />
 
-          {info}
-          {mutingInfo}
-          {actionBtn}
+                      <dd className={pair.get('verified_at') && 'verified'} title={pair.get('value_plain')}>
+                        {pair.get('verified_at') && <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(pair.get('verified_at'), dateFormatOptions) })}><Icon id='check' className='verified__mark' /></span>} <span dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} />
+                      </dd>
+                    </dl>
+                  ))}
+                </div>
+              )}
+
+              {account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content' dangerouslySetInnerHTML={content} />}
+            </div>
+
+            <div className='account__header__extra__links'>
+              <NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/accounts/${account.get('id')}`} title={intl.formatNumber(account.get('statuses_count'))}>
+                <strong>{shortNumberFormat(account.get('statuses_count'))}</strong> <FormattedMessage id='account.posts' defaultMessage='Toots' />
+              </NavLink>
+
+              <NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/following`} title={intl.formatNumber(account.get('following_count'))}>
+                <strong>{shortNumberFormat(account.get('following_count'))}</strong> <FormattedMessage id='account.follows' defaultMessage='Follows' />
+              </NavLink>
+
+              <NavLink exact activeClassName='active' to={`/accounts/${account.get('id')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
+                <strong>{shortNumberFormat(account.get('followers_count'))}</strong> <FormattedMessage id='account.followers' defaultMessage='Followers' />
+              </NavLink>
+            </div>
+          </div>
         </div>
       </div>
     );