From 8257bdcbca9b09ca0d56ae3ac4e9053cc8692077 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Thu, 16 Apr 2026 20:05:36 +0200 Subject: [PATCH] [Glitch] Implement new Collection inclusion rules in Collection accounts editor Port a40b07164019ee38290eb2167b648a9f46c2634a to glitch-soc Signed-off-by: Claire --- .../features/collections/editor/accounts.tsx | 104 ++++++------------ .../glitch/features/lists/members.tsx | 5 +- .../follow_to_collection.tsx | 43 -------- .../components/confirmation_modals/index.ts | 1 - .../features/ui/components/modal_root.jsx | 2 - .../glitch/hooks/useSearchAccounts.ts | 10 +- 6 files changed, 41 insertions(+), 124 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/ui/components/confirmation_modals/follow_to_collection.tsx diff --git a/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx b/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx index 26960e69ab..879e46c241 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/accounts.tsx @@ -4,11 +4,8 @@ import { FormattedMessage, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; -import { showAlertForError } from 'flavours/glitch/actions/alerts'; -import { openModal } from 'flavours/glitch/actions/modal'; -import { apiFollowAccount } from 'flavours/glitch/api/accounts'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; -import { Account } from 'flavours/glitch/components/account'; +import { AccountListItem } from 'flavours/glitch/components/account_list_item'; import { Avatar } from 'flavours/glitch/components/avatar'; import { Button } from 'flavours/glitch/components/button'; import { DisplayName } from 'flavours/glitch/components/display_name'; @@ -24,19 +21,18 @@ import { } from 'flavours/glitch/components/scrollable_list/components'; import { useAccount } from 'flavours/glitch/hooks/useAccount'; import { useSearchAccounts } from 'flavours/glitch/hooks/useSearchAccounts'; -import { me } from 'flavours/glitch/initial_state'; import { addCollectionItem, getCollectionItemIds, removeCollectionItem, updateCollectionEditorField, } from 'flavours/glitch/reducers/slices/collections'; -import { store, useAppDispatch, useAppSelector } from 'flavours/glitch/store'; +import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import classes from './styles.module.scss'; import { WizardStepTitle } from './wizard_step_title'; -const MAX_ACCOUNT_COUNT = 3; +const MAX_ACCOUNT_COUNT = 25; const AddedAccountItem: React.FC<{ accountId: string; @@ -46,20 +42,24 @@ const AddedAccountItem: React.FC<{ onRemove(accountId); }, [accountId, onRemove]); - return ( - + const renderButton = useCallback( + () => ( - + ), + [handleRemoveAccount], ); + + return ; }; interface SuggestionItem { id: string; + isDisabled?: boolean; } const SuggestedAccountItem: React.FC = ({ id }) => { @@ -80,6 +80,7 @@ const renderAccountItem = (item: SuggestionItem) => ( ); const getItemId = (item: SuggestionItem) => item.id; +const getIsItemDisabled = (item: SuggestionItem) => item.isDisabled ?? false; export const CollectionAccounts: React.FC<{ collection?: ApiCollectionJSON | null; @@ -109,21 +110,22 @@ export const CollectionAccounts: React.FC<{ const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT; const { - accountIds: suggestedAccountIds, + accounts: suggestedAccounts, isLoading: isLoadingSuggestions, searchAccounts, resetAccounts, } = useSearchAccounts({ withRelationships: true, - filterResults: (account) => - !accountIds.includes(account.id) && - // Only suggest accounts who allow being featured/recommended - account.feature_approval.current_user === 'automatic', + // Don't suggest accounts that were already added + filterResults: (account) => !accountIds.includes(account.id), }); - const suggestedItems = suggestedAccountIds.map((id) => ({ + const suggestedItems = suggestedAccounts.map(({ id, feature_approval }) => ({ id, - isDisabled: accountIds.includes(id), + // Disable accounts who can't be added to a collection + isDisabled: !['automatic', 'manual'].includes( + feature_approval.current_user, + ), })); const handleSearchValueChange = useCallback( @@ -143,43 +145,6 @@ export const CollectionAccounts: React.FC<{ [], ); - const relationships = useAppSelector((state) => state.relationships); - - const confirmFollowStatus = useCallback( - (accountId: string, onFollowing: () => void) => { - const relationship = relationships.get(accountId); - - if (!relationship) { - return; - } - - if ( - accountId === me || - relationship.following || - relationship.requested - ) { - onFollowing(); - } else { - dispatch( - openModal({ - modalType: 'CONFIRM_FOLLOW_TO_COLLECTION', - modalProps: { - accountId, - onConfirm: () => { - apiFollowAccount(accountId) - .then(onFollowing) - .catch((err: unknown) => { - store.dispatch(showAlertForError(err)); - }); - }, - }, - }), - ); - } - }, - [dispatch, relationships], - ); - const removeAccountItem = useCallback( (accountId: string) => { dispatch( @@ -194,16 +159,14 @@ export const CollectionAccounts: React.FC<{ const addAccountItem = useCallback( (item: SuggestionItem) => { - confirmFollowStatus(item.id, () => { - dispatch( - updateCollectionEditorField({ - field: 'accountIds', - value: [...accountIds, item.id], - }), - ); - }); + dispatch( + updateCollectionEditorField({ + field: 'accountIds', + value: [...accountIds, item.id], + }), + ); }, - [accountIds, confirmFollowStatus, dispatch], + [accountIds, dispatch], ); const instantRemoveAccountItem = useCallback( @@ -230,15 +193,13 @@ export const CollectionAccounts: React.FC<{ const instantAddAccountItem = useCallback( (item: SuggestionItem) => { - confirmFollowStatus(item.id, () => { - if (id) { - void dispatch( - addCollectionItem({ collectionId: id, accountId: item.id }), - ); - } - }); + if (id) { + void dispatch( + addCollectionItem({ collectionId: id, accountId: item.id }), + ); + } }, - [confirmFollowStatus, dispatch, id], + [dispatch, id], ); const handleRemoveAccountItem = useCallback( @@ -310,6 +271,7 @@ export const CollectionAccounts: React.FC<{ isLoading={isLoadingSuggestions} items={suggestedItems} getItemId={getItemId} + getIsItemDisabled={getIsItemDisabled} renderItem={renderAccountItem} onSelectItem={handleSelectItem} status={ diff --git a/app/javascript/flavours/glitch/features/lists/members.tsx b/app/javascript/flavours/glitch/features/lists/members.tsx index 1a18e8e499..9df96cf7c2 100644 --- a/app/javascript/flavours/glitch/features/lists/members.tsx +++ b/app/javascript/flavours/glitch/features/lists/members.tsx @@ -164,7 +164,7 @@ const ListMembers: React.FC<{ const [mode, setMode] = useState('remove'); const { - accountIds: searchAccountIds, + accounts: accountsFromSearch, isLoading: loadingSearchResults, searchAccounts: handleSearch, } = useSearchAccounts({ @@ -177,6 +177,7 @@ const ListMembers: React.FC<{ } }, }); + const accountIdsFromSearch = accountsFromSearch.map((item) => item.id); useEffect(() => { if (id) { @@ -220,7 +221,7 @@ const ListMembers: React.FC<{ let displayedAccountIds: string[]; if (mode === 'add' && searching) { - displayedAccountIds = searchAccountIds; + displayedAccountIds = accountIdsFromSearch; } else { displayedAccountIds = accountIds; } diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/follow_to_collection.tsx b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/follow_to_collection.tsx deleted file mode 100644 index ade85c174b..0000000000 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/follow_to_collection.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; - -import { useAccount } from 'flavours/glitch/hooks/useAccount'; - -import type { BaseConfirmationModalProps } from './confirmation_modal'; -import { ConfirmationModal } from './confirmation_modal'; - -const messages = defineMessages({ - title: { - id: 'confirmations.follow_to_collection.title', - defaultMessage: 'Follow account?', - }, - confirm: { - id: 'confirmations.follow_to_collection.confirm', - defaultMessage: 'Follow and add to collection', - }, -}); - -export const ConfirmFollowToCollectionModal: React.FC< - { - accountId: string; - onConfirm: () => void; - } & BaseConfirmationModalProps -> = ({ accountId, onConfirm, onClose }) => { - const intl = useIntl(); - const account = useAccount(accountId); - - return ( - @{account?.acct} }} - /> - } - confirm={intl.formatMessage(messages.confirm)} - onConfirm={onConfirm} - onClose={onClose} - /> - ); -}; diff --git a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts index 4011b4db06..c27597fb52 100644 --- a/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts +++ b/app/javascript/flavours/glitch/features/ui/components/confirmation_modals/index.ts @@ -13,7 +13,6 @@ export { ConfirmUnblockModal } from './unblock'; export { ConfirmClearNotificationsModal } from './clear_notifications'; export { ConfirmLogOutModal } from './log_out'; export { ConfirmFollowToListModal } from './follow_to_list'; -export { ConfirmFollowToCollectionModal } from './follow_to_collection'; export { ConfirmMissingAltTextModal } from './missing_alt_text'; export { ConfirmRevokeQuoteModal } from './revoke_quote'; export { QuietPostQuoteInfoModal } from './quiet_post_quote_info'; diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx index ce2fd0ed8e..77c559ba0f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx @@ -40,7 +40,6 @@ import { ConfirmClearNotificationsModal, ConfirmLogOutModal, ConfirmFollowToListModal, - ConfirmFollowToCollectionModal, ConfirmMissingAltTextModal, ConfirmRevokeQuoteModal, QuietPostQuoteInfoModal, @@ -75,7 +74,6 @@ export const MODAL_COMPONENTS = { 'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }), 'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }), 'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }), - 'CONFIRM_FOLLOW_TO_COLLECTION': () => Promise.resolve({ default: ConfirmFollowToCollectionModal }), 'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }), 'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }), 'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }), diff --git a/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts b/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts index e7e091bfcc..f9af2a1eb6 100644 --- a/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts +++ b/app/javascript/flavours/glitch/hooks/useSearchAccounts.ts @@ -21,7 +21,7 @@ export function useSearchAccounts({ } = {}) { const dispatch = useAppDispatch(); - const [accountIds, setAccountIds] = useState([]); + const [accounts, setAccounts] = useState([]); const [loadingState, setLoadingState] = useState< 'idle' | 'loading' | 'error' >('idle'); @@ -37,7 +37,7 @@ export function useSearchAccounts({ if (value.trim().length === 0) { onSettled?.(''); if (resetOnInputClear) { - setAccountIds([]); + setAccounts([]); } return; } @@ -60,7 +60,7 @@ export function useSearchAccounts({ if (withRelationships) { dispatch(fetchRelationships(accountIds)); } - setAccountIds(accountIds); + setAccounts(accounts); setLoadingState('idle'); onSettled?.(value); }) @@ -74,13 +74,13 @@ export function useSearchAccounts({ ); const resetAccounts = useCallback(() => { - setAccountIds([]); + setAccounts([]); }, []); return { searchAccounts, resetAccounts, - accountIds, + accounts, isLoading: loadingState === 'loading', isError: loadingState === 'error', };