[Glitch] Allow hiding featured tab from empty state

Port 19ef4e5c40818278156f9be9a4fd76d18cd44822 to glitch-soc

Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
diondiondion 2026-04-09 12:59:11 +02:00 committed by Claire
parent f84df36bd1
commit c2fe62be6c
4 changed files with 137 additions and 19 deletions

View File

@ -1,13 +1,18 @@
import { useCallback } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { openModal } from '@/flavours/glitch/actions/modal';
import { Button } from '@/flavours/glitch/components/button';
import { EmptyState } from '@/flavours/glitch/components/empty_state'; import { EmptyState } from '@/flavours/glitch/components/empty_state';
import { LimitedAccountHint } from '@/flavours/glitch/features/account_timeline/components/limited_account_hint'; import { LimitedAccountHint } from '@/flavours/glitch/features/account_timeline/components/limited_account_hint';
import { areCollectionsEnabled } from '@/flavours/glitch/features/collections/utils'; import { areCollectionsEnabled } from '@/flavours/glitch/features/collections/utils';
import { useCurrentAccountId } from '@/flavours/glitch/hooks/useAccountId'; import { useCurrentAccountId } from '@/flavours/glitch/hooks/useAccountId';
import { useTheme } from '@/flavours/glitch/hooks/useTheme'; import { useTheme } from '@/flavours/glitch/hooks/useTheme';
import { useAppDispatch } from '@/flavours/glitch/store';
import ElephantDarkImage from '@/images/elephant_ui_dark.svg?react'; import ElephantDarkImage from '@/images/elephant_ui_dark.svg?react';
import ElephantLightImage from '@/images/elephant_ui_light.svg?react'; import ElephantLightImage from '@/images/elephant_ui_light.svg?react';
@ -16,6 +21,7 @@ interface EmptyMessageProps {
hidden: boolean; hidden: boolean;
blockedBy: boolean; blockedBy: boolean;
accountId?: string; accountId?: string;
withImage?: boolean;
} }
export const EmptyMessage: React.FC<EmptyMessageProps> = ({ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
@ -23,6 +29,7 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
suspended, suspended,
hidden, hidden,
blockedBy, blockedBy,
withImage = true,
}) => { }) => {
const { acct } = useParams<{ acct?: string }>(); const { acct } = useParams<{ acct?: string }>();
const me = useCurrentAccountId(); const me = useCurrentAccountId();
@ -30,6 +37,17 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
const ElephantImage = const ElephantImage =
theme === 'dark' ? ElephantDarkImage : ElephantLightImage; theme === 'dark' ? ElephantDarkImage : ElephantLightImage;
const dispatch = useAppDispatch();
const confirmHideFeaturedTab = useCallback(() => {
void dispatch(
openModal({
modalType: 'ACCOUNT_HIDE_FEATURED_TAB',
modalProps: {},
}),
);
}, [dispatch]);
if (!accountId) { if (!accountId) {
return null; return null;
} }
@ -39,7 +57,7 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
const hasCollections = areCollectionsEnabled(); const hasCollections = areCollectionsEnabled();
const image = <ElephantImage />; const image = withImage && <ElephantImage />;
if (me === accountId) { if (me === accountId) {
if (hasCollections) { if (hasCollections) {
@ -66,6 +84,12 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
defaultMessage='Create a collection' defaultMessage='Create a collection'
/> />
</Link> </Link>
<Button secondary onClick={confirmHideFeaturedTab}>
<FormattedMessage
id='empty_column.account_featured_self.no_collections_hide_tab'
defaultMessage='Hide this tab instead'
/>
</Button>
</EmptyState> </EmptyState>
); );
} else { } else {

View File

@ -35,6 +35,8 @@ import { areCollectionsEnabled } from '../collections/utils';
import { EmptyMessage } from './components/empty_message'; import { EmptyMessage } from './components/empty_message';
import { Subheading, SubheadingLink } from './components/subheading'; import { Subheading, SubheadingLink } from './components/subheading';
const collectionsEnabled = areCollectionsEnabled();
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
multiColumn, multiColumn,
}) => { }) => {
@ -56,13 +58,12 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
if (accountId) { if (accountId) {
void dispatch(fetchEndorsedAccounts({ accountId })); void dispatch(fetchEndorsedAccounts({ accountId }));
if (areCollectionsEnabled()) { if (collectionsEnabled) {
void dispatch(fetchAccountCollections({ accountId })); void dispatch(fetchAccountCollections({ accountId }));
} }
} }
}, [accountId, dispatch]); }, [accountId, dispatch]);
const isLoading = !accountId;
const featuredAccountIds = useAppSelector( const featuredAccountIds = useAppSelector(
(state) => (state) =>
state.user_lists.getIn( state.user_lists.getIn(
@ -70,8 +71,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
ImmutableList(), ImmutableList(),
) as ImmutableList<string>, ) as ImmutableList<string>,
); );
const { collections, status } = useAppSelector((state) => const { collections, status: collectionsLoadStatus } = useAppSelector(
selectAccountCollections(state, accountId ?? null), (state) => selectAccountCollections(state, accountId ?? null),
); );
const listedCollections = collections.filter( const listedCollections = collections.filter(
// Hide unlisted and empty collections to avoid confusion // Hide unlisted and empty collections to avoid confusion
@ -80,6 +81,15 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
(item) => item.discoverable && !!item.item_count, (item) => item.discoverable && !!item.item_count,
); );
const hasCollections =
collectionsEnabled &&
collectionsLoadStatus === 'idle' &&
listedCollections.length > 0;
const hasFeaturedAccounts = !featuredAccountIds.isEmpty();
const isLoading = !accountId || collectionsLoadStatus !== 'idle';
if (accountId === null) { if (accountId === null) {
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />; return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
} }
@ -94,7 +104,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
); );
} }
if (featuredAccountIds.isEmpty() && listedCollections.length === 0) { if (!hasFeaturedAccounts && !hasCollections) {
return ( return (
<AccountFeaturedWrapper accountId={accountId}> <AccountFeaturedWrapper accountId={accountId}>
<EmptyMessage <EmptyMessage
@ -138,7 +148,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
</ItemList> </ItemList>
</> </>
)} )}
{listedCollections.length > 0 && status === 'idle' && ( {collectionsEnabled && (
<> <>
<Subheading as='header'> <Subheading as='header'>
<h2> <h2>
@ -154,18 +164,28 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
/> />
</SubheadingLink> </SubheadingLink>
</Subheading> </Subheading>
<ItemList> {hasCollections ? (
{listedCollections.map((item, index) => ( <ItemList>
<CollectionListItem {listedCollections.map((item, index) => (
key={item.id} <CollectionListItem
collection={item} key={item.id}
withoutBorder={index === listedCollections.length - 1} collection={item}
withAuthorHandle={false} withoutBorder={index === listedCollections.length - 1}
positionInList={index + 1} withAuthorHandle={false}
listSize={listedCollections.length} positionInList={index + 1}
/> listSize={listedCollections.length}
))} />
</ItemList> ))}
</ItemList>
) : (
<EmptyMessage
withImage={false}
blockedBy={blockedBy}
hidden={hidden}
suspended={suspended}
accountId={accountId}
/>
)}
</> </>
)} )}
<RemoteHint accountId={accountId} /> <RemoteHint accountId={accountId} />

View File

@ -0,0 +1,73 @@
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router';
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
import { useCurrentAccountId } from '@/flavours/glitch/hooks/useAccountId';
import { domain } from '@/flavours/glitch/initial_state';
import { patchProfile } from '@/flavours/glitch/reducers/slices/profile_edit';
import { useAppDispatch } from 'flavours/glitch/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
import { ConfirmationModal } from './confirmation_modal';
const messages = defineMessages({
title: {
id: 'confirmations.hide_featured_tab.title',
defaultMessage: 'Hide "Featured" tab?',
},
intro: {
id: 'confirmations.hide_featured_tab.intro',
defaultMessage:
'You can change this at any time under <i>Edit profile > Profile tab settings</i>.',
},
message: {
id: 'confirmations.hide_featured_tab.message',
defaultMessage:
'This will hide the tab for users on {serverName} and other servers running the latest version of Mastodon. Other displays may vary.',
},
confirm: {
id: 'confirmations.hide_featured_tab.confirm',
defaultMessage: 'Hide tab',
},
});
export const ConfirmHideFeaturedTabModal: React.FC<
BaseConfirmationModalProps
> = ({ onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const history = useHistory();
const currentAccountId = useCurrentAccountId();
const { acct: currentUserName } = useAccount(currentAccountId) ?? {};
const onConfirm = useCallback(() => {
void dispatch(patchProfile({ show_featured: false }));
history.push(`/@${currentUserName}`);
}, [currentUserName, dispatch, history]);
return (
<ConfirmationModal
title={intl.formatMessage(messages.title)}
extraContent={
<div className='prose'>
<p>
{intl.formatMessage(messages.intro, {
i: (words) => <i>{words}</i>,
})}
</p>
<p>
{intl.formatMessage(messages.message, {
serverName: domain,
})}
</p>
</div>
}
confirm={intl.formatMessage(messages.confirm)}
onConfirm={onConfirm}
onClose={onClose}
/>
);
};

View File

@ -113,6 +113,7 @@ export const MODAL_COMPONENTS = {
'ACCOUNT_EDIT_IMAGE_ALT': accountEditModal('ImageAltModal'), 'ACCOUNT_EDIT_IMAGE_ALT': accountEditModal('ImageAltModal'),
'ACCOUNT_EDIT_IMAGE_DELETE': accountEditModal('ImageDeleteModal'), 'ACCOUNT_EDIT_IMAGE_DELETE': accountEditModal('ImageDeleteModal'),
'ACCOUNT_EDIT_IMAGE_UPLOAD': accountEditModal('ImageUploadModal'), 'ACCOUNT_EDIT_IMAGE_UPLOAD': accountEditModal('ImageUploadModal'),
'ACCOUNT_HIDE_FEATURED_TAB': () => import('@/flavours/glitch/features/ui/components/confirmation_modals/hide_featured_tab').then(module => ({ default: module.ConfirmHideFeaturedTabModal })),
}; };
/** @arg {keyof import('@/flavours/glitch/features/account_edit/modals')} type */ /** @arg {keyof import('@/flavours/glitch/features/account_edit/modals')} type */