224 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			224 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { useCallback, useEffect, useState } from 'react';
 | |
| 
 | |
| import { FormattedMessage } from 'react-intl';
 | |
| 
 | |
| import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
 | |
| import DomainDisabledIcon from '@/material-icons/400-24px/domain_disabled.svg?react';
 | |
| import HistoryIcon from '@/material-icons/400-24px/history.svg?react';
 | |
| import PersonRemoveIcon from '@/material-icons/400-24px/person_remove.svg?react';
 | |
| import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
 | |
| import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
 | |
| import { blockAccount } from 'mastodon/actions/accounts';
 | |
| import { blockDomain } from 'mastodon/actions/domain_blocks';
 | |
| import { closeModal } from 'mastodon/actions/modal';
 | |
| import { apiRequest } from 'mastodon/api';
 | |
| import { Button } from 'mastodon/components/button';
 | |
| import { Icon } from 'mastodon/components/icon';
 | |
| import { LoadingIndicator } from 'mastodon/components/loading_indicator';
 | |
| import { ShortNumber } from 'mastodon/components/short_number';
 | |
| import { useAppDispatch } from 'mastodon/store';
 | |
| 
 | |
| interface DomainBlockPreviewResponse {
 | |
|   following_count: number;
 | |
|   followers_count: number;
 | |
| }
 | |
| 
 | |
| export const DomainBlockModal: React.FC<{
 | |
|   domain: string;
 | |
|   accountId: string;
 | |
|   acct: string;
 | |
| }> = ({ domain, accountId, acct }) => {
 | |
|   const dispatch = useAppDispatch();
 | |
|   const [loading, setLoading] = useState(true);
 | |
|   const [preview, setPreview] = useState<
 | |
|     DomainBlockPreviewResponse | 'error' | null
 | |
|   >(null);
 | |
| 
 | |
|   const handleClick = useCallback(() => {
 | |
|     if (loading) {
 | |
|       return; // Prevent destructive action before the preview finishes loading or times out
 | |
|     }
 | |
| 
 | |
|     dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | |
|     dispatch(blockDomain(domain));
 | |
|   }, [dispatch, loading, domain]);
 | |
| 
 | |
|   const handleSecondaryClick = useCallback(() => {
 | |
|     dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | |
|     dispatch(blockAccount(accountId));
 | |
|   }, [dispatch, accountId]);
 | |
| 
 | |
|   const handleCancel = useCallback(() => {
 | |
|     dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
 | |
|   }, [dispatch]);
 | |
| 
 | |
|   useEffect(() => {
 | |
|     setLoading(true);
 | |
| 
 | |
|     apiRequest<DomainBlockPreviewResponse>('GET', 'v1/domain_blocks/preview', {
 | |
|       params: { domain },
 | |
|       timeout: 5000,
 | |
|     })
 | |
|       .then((data) => {
 | |
|         setPreview(data);
 | |
|         setLoading(false);
 | |
|         return '';
 | |
|       })
 | |
|       .catch(() => {
 | |
|         setPreview('error');
 | |
|         setLoading(false);
 | |
|       });
 | |
|   }, [setPreview, setLoading, domain]);
 | |
| 
 | |
|   return (
 | |
|     <div className='modal-root__modal safety-action-modal' aria-live='polite'>
 | |
|       <div className='safety-action-modal__top'>
 | |
|         <div className='safety-action-modal__header'>
 | |
|           <div className='safety-action-modal__header__icon'>
 | |
|             <Icon id='' icon={DomainDisabledIcon} />
 | |
|           </div>
 | |
| 
 | |
|           <div>
 | |
|             <h1>
 | |
|               <FormattedMessage
 | |
|                 id='domain_block_modal.title'
 | |
|                 defaultMessage='Block domain?'
 | |
|               />
 | |
|             </h1>
 | |
|             <div>{domain}</div>
 | |
|           </div>
 | |
|         </div>
 | |
| 
 | |
|         <div className='safety-action-modal__bullet-points'>
 | |
|           {preview &&
 | |
|             preview !== 'error' &&
 | |
|             preview.followers_count + preview.following_count > 0 && (
 | |
|               <div>
 | |
|                 <div className='safety-action-modal__bullet-points__icon'>
 | |
|                   <Icon id='' icon={PersonRemoveIcon} />
 | |
|                 </div>
 | |
|                 <div>
 | |
|                   <strong>
 | |
|                     <FormattedMessage
 | |
|                       id='domain_block_modal.you_will_lose_num_followers'
 | |
|                       defaultMessage='You will lose {followersCount, plural, one {{followersCountDisplay} follower} other {{followersCountDisplay} followers}} and {followingCount, plural, one {{followingCountDisplay} person you follow} other {{followingCountDisplay} people you follow}}.'
 | |
|                       values={{
 | |
|                         followersCount: preview.followers_count,
 | |
|                         followersCountDisplay: (
 | |
|                           <ShortNumber value={preview.followers_count} />
 | |
|                         ),
 | |
|                         followingCount: preview.following_count,
 | |
|                         followingCountDisplay: (
 | |
|                           <ShortNumber value={preview.following_count} />
 | |
|                         ),
 | |
|                       }}
 | |
|                     />
 | |
|                   </strong>
 | |
|                 </div>
 | |
|               </div>
 | |
|             )}
 | |
| 
 | |
|           {preview === 'error' && (
 | |
|             <div>
 | |
|               <div className='safety-action-modal__bullet-points__icon'>
 | |
|                 <Icon id='' icon={PersonRemoveIcon} />
 | |
|               </div>
 | |
|               <div>
 | |
|                 <strong>
 | |
|                   <FormattedMessage
 | |
|                     id='domain_block_modal.you_will_lose_relationships'
 | |
|                     defaultMessage='You will lose all followers and people you follow from this server.'
 | |
|                   />
 | |
|                 </strong>
 | |
|               </div>
 | |
|             </div>
 | |
|           )}
 | |
| 
 | |
|           <div className='safety-action-modal__bullet-points--deemphasized'>
 | |
|             <div className='safety-action-modal__bullet-points__icon'>
 | |
|               <Icon id='' icon={CampaignIcon} />
 | |
|             </div>
 | |
|             <div>
 | |
|               <FormattedMessage
 | |
|                 id='domain_block_modal.they_wont_know'
 | |
|                 defaultMessage="They won't know they've been blocked."
 | |
|               />
 | |
|             </div>
 | |
|           </div>
 | |
| 
 | |
|           <div className='safety-action-modal__bullet-points--deemphasized'>
 | |
|             <div className='safety-action-modal__bullet-points__icon'>
 | |
|               <Icon id='' icon={VisibilityOffIcon} />
 | |
|             </div>
 | |
|             <div>
 | |
|               <FormattedMessage
 | |
|                 id='domain_block_modal.you_wont_see_posts'
 | |
|                 defaultMessage="You won't see posts or notifications from users on this server."
 | |
|               />
 | |
|             </div>
 | |
|           </div>
 | |
| 
 | |
|           <div className='safety-action-modal__bullet-points--deemphasized'>
 | |
|             <div className='safety-action-modal__bullet-points__icon'>
 | |
|               <Icon id='' icon={ReplyIcon} />
 | |
|             </div>
 | |
|             <div>
 | |
|               <FormattedMessage
 | |
|                 id='domain_block_modal.they_cant_follow'
 | |
|                 defaultMessage='Nobody from this server can follow you.'
 | |
|               />
 | |
|             </div>
 | |
|           </div>
 | |
| 
 | |
|           <div className='safety-action-modal__bullet-points--deemphasized'>
 | |
|             <div className='safety-action-modal__bullet-points__icon'>
 | |
|               <Icon id='' icon={HistoryIcon} />
 | |
|             </div>
 | |
|             <div>
 | |
|               <FormattedMessage
 | |
|                 id='domain_block_modal.they_can_interact_with_old_posts'
 | |
|                 defaultMessage='People from this server can interact with your old posts.'
 | |
|               />
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       <div className='safety-action-modal__bottom'>
 | |
|         <div className='safety-action-modal__actions'>
 | |
|           <Button onClick={handleSecondaryClick} secondary>
 | |
|             <FormattedMessage
 | |
|               id='domain_block_modal.block_account_instead'
 | |
|               defaultMessage='Block @{name} instead'
 | |
|               values={{ name: acct.split('@')[0] }}
 | |
|             />
 | |
|           </Button>
 | |
| 
 | |
|           <div className='spacer' />
 | |
| 
 | |
|           <button onClick={handleCancel} className='link-button'>
 | |
|             <FormattedMessage
 | |
|               id='confirmation_modal.cancel'
 | |
|               defaultMessage='Cancel'
 | |
|             />
 | |
|           </button>
 | |
| 
 | |
|           <Button onClick={handleClick} dangerous aria-busy={loading}>
 | |
|             {loading ? (
 | |
|               <LoadingIndicator />
 | |
|             ) : (
 | |
|               <FormattedMessage
 | |
|                 id='domain_block_modal.block'
 | |
|                 defaultMessage='Block server'
 | |
|               />
 | |
|             )}
 | |
|           </Button>
 | |
|         </div>
 | |
|       </div>
 | |
|     </div>
 | |
|   );
 | |
| };
 | |
| 
 | |
| // eslint-disable-next-line import/no-default-export
 | |
| export default DomainBlockModal;
 |