270 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			270 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import type { MouseEventHandler } from 'react';
 | 
						|
import { useCallback } from 'react';
 | 
						|
 | 
						|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
 | 
						|
 | 
						|
import classNames from 'classnames';
 | 
						|
import { Link } from 'react-router-dom';
 | 
						|
 | 
						|
import {
 | 
						|
  followAccount,
 | 
						|
  unfollowAccount,
 | 
						|
  unblockAccount,
 | 
						|
  unmuteAccount,
 | 
						|
} from 'mastodon/actions/accounts';
 | 
						|
import { openModal } from 'mastodon/actions/modal';
 | 
						|
import { Avatar } from 'mastodon/components/avatar';
 | 
						|
import { Button } from 'mastodon/components/button';
 | 
						|
import { DisplayName } from 'mastodon/components/display_name';
 | 
						|
import { ShortNumber } from 'mastodon/components/short_number';
 | 
						|
import { autoPlayGif, me } from 'mastodon/initial_state';
 | 
						|
import type { Account } from 'mastodon/models/account';
 | 
						|
import { makeGetAccount } from 'mastodon/selectors';
 | 
						|
import { useAppDispatch, useAppSelector } from 'mastodon/store';
 | 
						|
 | 
						|
const messages = defineMessages({
 | 
						|
  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
 | 
						|
  follow: { id: 'account.follow', defaultMessage: 'Follow' },
 | 
						|
  cancel_follow_request: {
 | 
						|
    id: 'account.cancel_follow_request',
 | 
						|
    defaultMessage: 'Withdraw follow request',
 | 
						|
  },
 | 
						|
  cancelFollowRequestConfirm: {
 | 
						|
    id: 'confirmations.cancel_follow_request.confirm',
 | 
						|
    defaultMessage: 'Withdraw request',
 | 
						|
  },
 | 
						|
  requested: {
 | 
						|
    id: 'account.requested',
 | 
						|
    defaultMessage: 'Awaiting approval. Click to cancel follow request',
 | 
						|
  },
 | 
						|
  unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
 | 
						|
  unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
 | 
						|
  unfollowConfirm: {
 | 
						|
    id: 'confirmations.unfollow.confirm',
 | 
						|
    defaultMessage: 'Unfollow',
 | 
						|
  },
 | 
						|
  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
 | 
						|
});
 | 
						|
 | 
						|
const getAccount = makeGetAccount();
 | 
						|
 | 
						|
export const AccountCard: React.FC<{ accountId: string }> = ({ accountId }) => {
 | 
						|
  const intl = useIntl();
 | 
						|
  const account = useAppSelector((s) => getAccount(s, accountId));
 | 
						|
  const dispatch = useAppDispatch();
 | 
						|
 | 
						|
  const handleMouseEnter = useCallback<MouseEventHandler>(
 | 
						|
    ({ currentTarget }) => {
 | 
						|
      if (autoPlayGif) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      const emojis =
 | 
						|
        currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji');
 | 
						|
 | 
						|
      emojis.forEach((emoji) => {
 | 
						|
        const original = emoji.getAttribute('data-original');
 | 
						|
        if (original) emoji.src = original;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    [],
 | 
						|
  );
 | 
						|
 | 
						|
  const handleMouseLeave = useCallback<MouseEventHandler>(
 | 
						|
    ({ currentTarget }) => {
 | 
						|
      if (autoPlayGif) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      const emojis =
 | 
						|
        currentTarget.querySelectorAll<HTMLImageElement>('.custom-emoji');
 | 
						|
 | 
						|
      emojis.forEach((emoji) => {
 | 
						|
        const staticUrl = emoji.getAttribute('data-static');
 | 
						|
        if (staticUrl) emoji.src = staticUrl;
 | 
						|
      });
 | 
						|
    },
 | 
						|
    [],
 | 
						|
  );
 | 
						|
 | 
						|
  const handleFollow = useCallback(() => {
 | 
						|
    if (!account) return;
 | 
						|
 | 
						|
    if (account.getIn(['relationship', 'following'])) {
 | 
						|
      dispatch(
 | 
						|
        openModal({
 | 
						|
          modalType: 'CONFIRM',
 | 
						|
          modalProps: {
 | 
						|
            message: (
 | 
						|
              <FormattedMessage
 | 
						|
                id='confirmations.unfollow.message'
 | 
						|
                defaultMessage='Are you sure you want to unfollow {name}?'
 | 
						|
                values={{ name: <strong>@{account.get('acct')}</strong> }}
 | 
						|
              />
 | 
						|
            ),
 | 
						|
            confirm: intl.formatMessage(messages.unfollowConfirm),
 | 
						|
            onConfirm: () => {
 | 
						|
              dispatch(unfollowAccount(account.get('id')));
 | 
						|
            },
 | 
						|
          },
 | 
						|
        }),
 | 
						|
      );
 | 
						|
    } else if (account.getIn(['relationship', 'requested'])) {
 | 
						|
      dispatch(
 | 
						|
        openModal({
 | 
						|
          modalType: 'CONFIRM',
 | 
						|
          modalProps: {
 | 
						|
            message: (
 | 
						|
              <FormattedMessage
 | 
						|
                id='confirmations.cancel_follow_request.message'
 | 
						|
                defaultMessage='Are you sure you want to withdraw your request to follow {name}?'
 | 
						|
                values={{ name: <strong>@{account.get('acct')}</strong> }}
 | 
						|
              />
 | 
						|
            ),
 | 
						|
            confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
 | 
						|
            onConfirm: () => {
 | 
						|
              dispatch(unfollowAccount(account.get('id')));
 | 
						|
            },
 | 
						|
          },
 | 
						|
        }),
 | 
						|
      );
 | 
						|
    } else {
 | 
						|
      dispatch(followAccount(account.get('id')));
 | 
						|
    }
 | 
						|
  }, [account, dispatch, intl]);
 | 
						|
 | 
						|
  const handleBlock = useCallback(() => {
 | 
						|
    if (account?.relationship?.blocking) {
 | 
						|
      dispatch(unblockAccount(account.get('id')));
 | 
						|
    }
 | 
						|
  }, [account, dispatch]);
 | 
						|
 | 
						|
  const handleMute = useCallback(() => {
 | 
						|
    if (account?.relationship?.muting) {
 | 
						|
      dispatch(unmuteAccount(account.get('id')));
 | 
						|
    }
 | 
						|
  }, [account, dispatch]);
 | 
						|
 | 
						|
  const handleEditProfile = useCallback(() => {
 | 
						|
    window.open('/settings/profile', '_blank');
 | 
						|
  }, []);
 | 
						|
 | 
						|
  if (!account) return null;
 | 
						|
 | 
						|
  let actionBtn;
 | 
						|
 | 
						|
  if (me !== account.get('id')) {
 | 
						|
    if (!account.get('relationship')) {
 | 
						|
      // Wait until the relationship is loaded
 | 
						|
      actionBtn = '';
 | 
						|
    } else if (account.getIn(['relationship', 'requested'])) {
 | 
						|
      actionBtn = (
 | 
						|
        <Button
 | 
						|
          text={intl.formatMessage(messages.cancel_follow_request)}
 | 
						|
          title={intl.formatMessage(messages.requested)}
 | 
						|
          onClick={handleFollow}
 | 
						|
        />
 | 
						|
      );
 | 
						|
    } else if (account.getIn(['relationship', 'muting'])) {
 | 
						|
      actionBtn = (
 | 
						|
        <Button
 | 
						|
          text={intl.formatMessage(messages.unmute)}
 | 
						|
          onClick={handleMute}
 | 
						|
        />
 | 
						|
      );
 | 
						|
    } else if (!account.getIn(['relationship', 'blocking'])) {
 | 
						|
      actionBtn = (
 | 
						|
        <Button
 | 
						|
          disabled={account.relationship?.blocked_by}
 | 
						|
          className={classNames({
 | 
						|
            'button--destructive': account.getIn(['relationship', 'following']),
 | 
						|
          })}
 | 
						|
          text={intl.formatMessage(
 | 
						|
            account.getIn(['relationship', 'following'])
 | 
						|
              ? messages.unfollow
 | 
						|
              : messages.follow,
 | 
						|
          )}
 | 
						|
          onClick={handleFollow}
 | 
						|
        />
 | 
						|
      );
 | 
						|
    } else if (account.getIn(['relationship', 'blocking'])) {
 | 
						|
      actionBtn = (
 | 
						|
        <Button
 | 
						|
          text={intl.formatMessage(messages.unblock)}
 | 
						|
          onClick={handleBlock}
 | 
						|
        />
 | 
						|
      );
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    actionBtn = (
 | 
						|
      <Button
 | 
						|
        text={intl.formatMessage(messages.edit_profile)}
 | 
						|
        onClick={handleEditProfile}
 | 
						|
      />
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  return (
 | 
						|
    <div className='account-card'>
 | 
						|
      <Link to={`/@${account.get('acct')}`} className='account-card__permalink'>
 | 
						|
        <div className='account-card__header'>
 | 
						|
          <img
 | 
						|
            src={
 | 
						|
              autoPlayGif ? account.get('header') : account.get('header_static')
 | 
						|
            }
 | 
						|
            alt=''
 | 
						|
          />
 | 
						|
        </div>
 | 
						|
 | 
						|
        <div className='account-card__title'>
 | 
						|
          <div className='account-card__title__avatar'>
 | 
						|
            <Avatar account={account as Account} size={56} />
 | 
						|
          </div>
 | 
						|
          <DisplayName account={account as Account} />
 | 
						|
        </div>
 | 
						|
      </Link>
 | 
						|
 | 
						|
      {account.get('note').length > 0 && (
 | 
						|
        <div
 | 
						|
          className='account-card__bio translate'
 | 
						|
          onMouseEnter={handleMouseEnter}
 | 
						|
          onMouseLeave={handleMouseLeave}
 | 
						|
          dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
 | 
						|
        />
 | 
						|
      )}
 | 
						|
 | 
						|
      <div className='account-card__actions'>
 | 
						|
        <div className='account-card__counters'>
 | 
						|
          <div className='account-card__counters__item'>
 | 
						|
            <ShortNumber value={account.get('statuses_count')} />
 | 
						|
            <small>
 | 
						|
              <FormattedMessage id='account.posts' defaultMessage='Posts' />
 | 
						|
            </small>
 | 
						|
          </div>
 | 
						|
 | 
						|
          <div className='account-card__counters__item'>
 | 
						|
            <ShortNumber value={account.get('followers_count')} />{' '}
 | 
						|
            <small>
 | 
						|
              <FormattedMessage
 | 
						|
                id='account.followers'
 | 
						|
                defaultMessage='Followers'
 | 
						|
              />
 | 
						|
            </small>
 | 
						|
          </div>
 | 
						|
 | 
						|
          <div className='account-card__counters__item'>
 | 
						|
            <ShortNumber value={account.get('following_count')} />{' '}
 | 
						|
            <small>
 | 
						|
              <FormattedMessage
 | 
						|
                id='account.following'
 | 
						|
                defaultMessage='Following'
 | 
						|
              />
 | 
						|
            </small>
 | 
						|
          </div>
 | 
						|
        </div>
 | 
						|
 | 
						|
        <div className='account-card__actions__button'>{actionBtn}</div>
 | 
						|
      </div>
 | 
						|
    </div>
 | 
						|
  );
 | 
						|
};
 |