Rework account and domain list screens (#244)

This commit is contained in:
Clovis 2023-08-12 18:51:07 +02:00 committed by GitHub
parent cd5cf427a2
commit f77516f9f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 62 deletions

View File

@ -4,10 +4,10 @@ import { useDispatch } from 'react-redux';
import { unblockDomain } from 'soapbox/actions/domain_blocks';
import IconButton from './icon_button';
import Icon from './icon';
import { Button } from './ui';
const messages = defineMessages({
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
});
@ -19,29 +19,27 @@ const Domain: React.FC<IDomain> = ({ domain }) => {
const dispatch = useDispatch();
const intl = useIntl();
// const onBlockDomain = () => {
// dispatch(openModal('CONFIRM', {
// icon: require('@tabler/icons/ban.svg'),
// heading: <FormattedMessage id='confirmations.domain_block.heading' defaultMessage='Block {domain}' values={{ domain }} />,
// message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />,
// confirm: intl.formatMessage(messages.blockDomainConfirm),
// onConfirm: () => dispatch(blockDomain(domain)),
// }));
// }
const handleDomainUnblock = () => {
dispatch(unblockDomain(domain));
};
return (
<div className='domain'>
<div className='domain__wrapper'>
<span className='domain__domain-name'>
<strong>{domain}</strong>
</span>
<div className='domain__wrapper flex justify-between items-center'>
<div className='flex gap-1 items-center'>
<Icon src={require('@tabler/icons/world.svg')} />
<strong className='text-gray-700 dark:text-white'>{domain}</strong>
</div>
<div className='domain__buttons'>
<IconButton active src={require('@tabler/icons/lock-open.svg')} title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={handleDomainUnblock} />
<Button theme='ghost' onClick={handleDomainUnblock}>
<div className='flex gap-1 items-center'>
<Icon src={require('@tabler/icons/lock-open.svg')} />
<span>
{ intl.formatMessage(messages.unblockDomain) }
</span>
</div>
</Button>
</div>
</div>
</div>

View File

@ -23,19 +23,12 @@ const Blocks: React.FC = () => {
const accountIds = useAppSelector((state) => state.user_lists.blocks.items);
const hasMore = useAppSelector((state) => !!state.user_lists.blocks.next);
const loading = useAppSelector((state) => state.user_lists.blocks.isLoading);
React.useEffect(() => {
dispatch(fetchBlocks());
}, []);
if (!accountIds) {
return (
<Column>
<Spinner />
</Column>
);
}
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
return (
@ -45,12 +38,16 @@ const Blocks: React.FC = () => {
onLoadMore={() => handleLoadMore(dispatch)}
hasMore={hasMore}
emptyMessage={emptyMessage}
itemClassName='pb-4'
itemClassName='flex flex-col gap-3 pb-4'
>
{accountIds.map((id) =>
<AccountContainer key={id} id={id} actionType='blocking' />,
)}
{
loading && <Spinner />
}
</ScrollableList>
</Column>
);
};

View File

@ -26,19 +26,13 @@ const DomainBlocks: React.FC = () => {
const domains = useAppSelector((state) => state.domain_lists.blocks.items);
const hasMore = useAppSelector((state) => !!state.domain_lists.blocks.next);
const loading = useAppSelector((state) => state.domain_lists.blocks.isLoading);
React.useEffect(() => {
dispatch(fetchDomainBlocks());
}, []);
if (!domains) {
return (
<Column>
<Spinner />
</Column>
);
}
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
return (
@ -48,10 +42,16 @@ const DomainBlocks: React.FC = () => {
onLoadMore={() => handleLoadMore(dispatch)}
hasMore={hasMore}
emptyMessage={emptyMessage}
itemClassName='flex flex-col gap-3'
>
{domains.map((domain) =>
<Domain key={domain} domain={domain} />,
)}
{domains.map((domain) => (
<div className='rounded p-1 bg-gray-100 dark:bg-slate-900'>
<Domain key={domain} domain={domain} />
</div>
))}
{
loading && <Spinner />
}
</ScrollableList>
</Column>
);

View File

@ -26,24 +26,18 @@ const FollowRequests: React.FC = () => {
const accountIds = useAppSelector((state) => state.user_lists.follow_requests.items);
const hasMore = useAppSelector((state) => !!state.user_lists.follow_requests.next);
const loading = useAppSelector((state) => state.user_lists.mutes.isLoading);
React.useEffect(() => {
dispatch(fetchFollowRequests());
}, []);
if (!accountIds) {
return (
<Column>
<Spinner />
</Column>
);
}
const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
return (
<Column icon='user-plus' label={intl.formatMessage(messages.heading)}>
<ScrollableList
className='flex flex-col gap-3'
scrollKey='follow_requests'
onLoadMore={() => handleLoadMore(dispatch)}
hasMore={hasMore}
@ -52,6 +46,9 @@ const FollowRequests: React.FC = () => {
{accountIds.map(id =>
<AccountAuthorize key={id} id={id} />,
)}
{
loading && <Spinner />
}
</ScrollableList>
</Column>
);

View File

@ -23,19 +23,13 @@ const Mutes: React.FC = () => {
const accountIds = useAppSelector((state) => state.user_lists.mutes.items);
const hasMore = useAppSelector((state) => !!state.user_lists.mutes.next);
const loading = useAppSelector((state) => state.user_lists.mutes.isLoading);
React.useEffect(() => {
dispatch(fetchMutes());
}, []);
if (!accountIds) {
return (
<Column>
<Spinner />
</Column>
);
}
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
return (
@ -45,12 +39,16 @@ const Mutes: React.FC = () => {
onLoadMore={() => handleLoadMore(dispatch)}
hasMore={hasMore}
emptyMessage={emptyMessage}
itemClassName='pb-4'
itemClassName='flex flex-col gap-3 pb-4'
>
{accountIds.map((id) =>
<AccountContainer key={id} id={id} actionType='muting' />,
)}
{
loading && <Spinner />
}
</ScrollableList>
</Column>
);
};

View File

@ -7,7 +7,7 @@
"account.badges.bot": "Robot",
"account.birthday": "Born {date}",
"account.birthday_today": "Birthday is today!",
"account.block": "Bloquer @{name}",
"account.block": "Bloquer",
"account.block_domain": "Tout masquer venant de {domain}",
"account.blocked": "Bloqué",
"account.chat": "Chat with @{name}",
@ -36,7 +36,7 @@
"account.member_since": "Joined {date}",
"account.mention": "Mentionner",
"account.moved_to": "{name} a déménagé vers:",
"account.mute": "Masquer @{name}",
"account.mute": "Masquer",
"account.muted": "Muted",
"account.never_active": "Jamais",
"account.no_fields": "Cette section est vide pour le moment.",
@ -57,12 +57,12 @@
"account.subscribe.failure": "An error occurred trying to subscribed to this account.",
"account.subscribe.success": "You have subscribed to this account.",
"account.today": "Aujourd'hui",
"account.unblock": "Débloquer @{name}",
"account.unblock_domain": "Ne plus masquer {domain}",
"account.unblock": "Débloquer",
"account.unblock_domain": "Ne plus masquer",
"account.unendorse": "Ne plus recommander sur le profil",
"account.unendorse.success": "You are no longer featuring @{acct}",
"account.unfollow": "Ne plus suivre",
"account.unmute": "Ne plus masquer @{name}",
"account.unmute": "Ne plus masquer",
"account.unsubscribe": "Unsubscribe to notifications from @{name}",
"account.unsubscribe.failure": "An error occurred trying to unsubscribed to this account.",
"account.unsubscribe.success": "You have unsubscribed from this account.",

View File

@ -4,6 +4,7 @@ import {
DOMAIN_BLOCKS_FETCH_SUCCESS,
DOMAIN_BLOCKS_EXPAND_SUCCESS,
DOMAIN_UNBLOCK_SUCCESS,
DOMAIN_BLOCKS_FETCH_REQUEST,
} from '../actions/domain_blocks';
import type { AnyAction } from 'redux';
@ -11,6 +12,7 @@ import type { AnyAction } from 'redux';
const BlocksRecord = ImmutableRecord({
items: ImmutableOrderedSet<string>(),
next: null as string | null,
isLoading: false,
});
const ReducerRecord = ImmutableRecord({
@ -21,8 +23,12 @@ type State = ReturnType<typeof ReducerRecord>;
export default function domainLists(state: State = ReducerRecord(), action: AnyAction) {
switch (action.type) {
case DOMAIN_BLOCKS_FETCH_REQUEST:
return state.setIn(['blocks', 'isLoading'], true);
case DOMAIN_BLOCKS_FETCH_SUCCESS:
return state.setIn(['blocks', 'items'], ImmutableOrderedSet(action.domains)).setIn(['blocks', 'next'], action.next);
return state
.setIn(['blocks', 'isLoading'], false)
.setIn(['blocks', 'items'], ImmutableOrderedSet(action.domains)).setIn(['blocks', 'next'], action.next);
case DOMAIN_BLOCKS_EXPAND_SUCCESS:
return state.updateIn(['blocks', 'items'], set => (set as ImmutableOrderedSet<string>).union(action.domains)).setIn(['blocks', 'next'], action.next);
case DOMAIN_UNBLOCK_SUCCESS:

View File

@ -16,10 +16,12 @@ import {
FOLLOW_REQUEST_REJECT_SUCCESS,
PINNED_ACCOUNTS_FETCH_SUCCESS,
BIRTHDAY_REMINDERS_FETCH_SUCCESS,
FOLLOW_REQUESTS_FETCH_REQUEST,
} from '../actions/accounts';
import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS,
BLOCKS_FETCH_REQUEST,
} from '../actions/blocks';
import {
DIRECTORY_FETCH_REQUEST,
@ -47,6 +49,7 @@ import {
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS,
MUTES_FETCH_REQUEST,
} from '../actions/mutes';
import {
NOTIFICATIONS_UPDATE,
@ -98,9 +101,15 @@ type Items = ImmutableOrderedSet<string>;
type NestedListPath = ['followers' | 'following' | 'reblogged_by' | 'favourited_by' | 'reactions' | 'groups' | 'groups_removed_accounts' | 'pinned' | 'birthday_reminders' | 'familiar_followers', string];
type ListPath = ['follow_requests' | 'blocks' | 'mutes' | 'directory'];
const loadingList = (state: State, path: ListPath) => {
const current = state.getIn(path) as List;
return state.setIn(path, current.set('isLoading', true));
};
const normalizeList = (state: State, path: NestedListPath | ListPath, accounts: APIEntity[], next?: string | null) => {
return state.setIn(path, ListRecord({
isLoading: false,
next,
items: ImmutableOrderedSet(accounts.map(item => item.id)),
}));
@ -108,13 +117,15 @@ const normalizeList = (state: State, path: NestedListPath | ListPath, accounts:
const appendToList = (state: State, path: NestedListPath | ListPath, accounts: APIEntity[], next: string | null) => {
return state.updateIn(path, map => {
return (map as List).set('next', next).update('items', list => (list as Items).concat(accounts.map(item => item.id)));
return (map as List)
.set('next', next).update('items', list => (list as Items).concat(accounts.map(item => item.id)));
});
};
const removeFromList = (state: State, path: NestedListPath | ListPath, accountId: string) => {
return state.updateIn(path, map => {
return (map as List).update('items', list => (list as Items).filterNot(item => item === accountId));
return (map as List)
.update('items', list => (list as Items).filterNot(item => item === accountId));
});
};
@ -126,6 +137,12 @@ const normalizeFollowRequest = (state: State, notification: APIEntity) => {
export default function userLists(state = ReducerRecord(), action: AnyAction) {
switch (action.type) {
case BLOCKS_FETCH_REQUEST:
return loadingList(state, ['blocks']);
case MUTES_FETCH_REQUEST:
return loadingList(state, ['mutes']);
case FOLLOW_REQUESTS_FETCH_REQUEST:
return loadingList(state, ['follow_requests']);
case FOLLOWERS_FETCH_SUCCESS:
return normalizeList(state, ['followers', action.id], action.accounts, action.next);
case FOLLOWERS_EXPAND_SUCCESS: