mirror of
https://git.kescher.at/CatCatNya/catstodon.git
synced 2024-11-22 12:58:06 +01:00
[Glitch] Add preview of followers removed in domain block modal in web UI
Port 3426ea2912
to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
7ef25ae53b
commit
7b290cee47
7 changed files with 232 additions and 107 deletions
|
@ -70,6 +70,7 @@ export async function apiRequest<ApiResponse = unknown>(
|
||||||
args: {
|
args: {
|
||||||
params?: RequestParamsOrData;
|
params?: RequestParamsOrData;
|
||||||
data?: RequestParamsOrData;
|
data?: RequestParamsOrData;
|
||||||
|
timeout?: number;
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const { data } = await api().request<ApiResponse>({
|
const { data } = await api().request<ApiResponse>({
|
||||||
|
|
|
@ -7,6 +7,7 @@ interface BaseProps
|
||||||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
||||||
block?: boolean;
|
block?: boolean;
|
||||||
secondary?: boolean;
|
secondary?: boolean;
|
||||||
|
dangerous?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PropsChildren extends PropsWithChildren<BaseProps> {
|
interface PropsChildren extends PropsWithChildren<BaseProps> {
|
||||||
|
@ -26,6 +27,7 @@ export const Button: React.FC<Props> = ({
|
||||||
disabled,
|
disabled,
|
||||||
block,
|
block,
|
||||||
secondary,
|
secondary,
|
||||||
|
dangerous,
|
||||||
className,
|
className,
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
|
@ -46,6 +48,7 @@ export const Button: React.FC<Props> = ({
|
||||||
className={classNames('button', className, {
|
className={classNames('button', className, {
|
||||||
'button-secondary': secondary,
|
'button-secondary': secondary,
|
||||||
'button--block': block,
|
'button--block': block,
|
||||||
|
'button--dangerous': dangerous,
|
||||||
})}
|
})}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
|
|
@ -99,7 +99,7 @@ export const BlockModal = ({ accountId, acct }) => {
|
||||||
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
|
<FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<Button onClick={handleClick} autoFocus>
|
<Button onClick={handleClick} dangerous autoFocus>
|
||||||
<FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' />
|
<FormattedMessage id='confirmations.block.confirm' defaultMessage='Block' />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
|
|
||||||
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 'flavours/glitch/actions/accounts';
|
|
||||||
import { blockDomain } from 'flavours/glitch/actions/domain_blocks';
|
|
||||||
import { closeModal } from 'flavours/glitch/actions/modal';
|
|
||||||
import { Button } from 'flavours/glitch/components/button';
|
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
|
||||||
|
|
||||||
export const DomainBlockModal = ({ domain, accountId, acct }) => {
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
|
||||||
dispatch(closeModal({ modalType: undefined, ignoreFocus: false }));
|
|
||||||
dispatch(blockDomain(domain));
|
|
||||||
}, [dispatch, 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]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='modal-root__modal safety-action-modal'>
|
|
||||||
<div className='safety-action-modal__top'>
|
|
||||||
<div className='safety-action-modal__header'>
|
|
||||||
<div className='safety-action-modal__header__icon'>
|
|
||||||
<Icon 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'>
|
|
||||||
<div>
|
|
||||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={CampaignIcon} /></div>
|
|
||||||
<div><FormattedMessage id='domain_block_modal.they_wont_know' defaultMessage="They won't know they've been blocked." /></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className='safety-action-modal__bullet-points__icon'><Icon 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>
|
|
||||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={PersonRemoveIcon} /></div>
|
|
||||||
<div><FormattedMessage id='domain_block_modal.you_will_lose_followers' defaultMessage='All your followers from this server will be removed.' /></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={ReplyIcon} /></div>
|
|
||||||
<div><FormattedMessage id='domain_block_modal.they_cant_follow' defaultMessage='Nobody from this server can follow you.' /></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div className='safety-action-modal__bullet-points__icon'><Icon 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} autoFocus>
|
|
||||||
<FormattedMessage id='domain_block_modal.block' defaultMessage='Block server' />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
DomainBlockModal.propTypes = {
|
|
||||||
domain: PropTypes.string.isRequired,
|
|
||||||
accountId: PropTypes.string.isRequired,
|
|
||||||
acct: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DomainBlockModal;
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
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 'flavours/glitch/actions/accounts';
|
||||||
|
import { blockDomain } from 'flavours/glitch/actions/domain_blocks';
|
||||||
|
import { closeModal } from 'flavours/glitch/actions/modal';
|
||||||
|
import { apiRequest } from 'flavours/glitch/api';
|
||||||
|
import { Button } from 'flavours/glitch/components/button';
|
||||||
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
|
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||||
|
import { useAppDispatch } from 'flavours/glitch/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 | 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(() => {
|
||||||
|
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.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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<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;
|
|
@ -81,6 +81,18 @@
|
||||||
outline: $ui-button-icon-focus-outline;
|
outline: $ui-button-icon-focus-outline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--dangerous {
|
||||||
|
background-color: var(--error-background-color);
|
||||||
|
color: var(--on-error-color);
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--error-active-background-color);
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&--destructive {
|
&--destructive {
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
|
@ -6687,6 +6699,14 @@ a.status-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--deemphasized {
|
||||||
|
color: $secondary-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
|
|
|
@ -117,4 +117,7 @@ $dismiss-overlay-width: 4rem;
|
||||||
--surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
|
--surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
|
||||||
--on-surface-color: #{transparentize($ui-base-color, 0.5)};
|
--on-surface-color: #{transparentize($ui-base-color, 0.5)};
|
||||||
--avatar-border-radius: 8px;
|
--avatar-border-radius: 8px;
|
||||||
|
--error-background-color: #{darken($error-red, 16%)};
|
||||||
|
--error-active-background-color: #{darken($error-red, 12%)};
|
||||||
|
--on-error-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue