[Glitch] Implement new Collection inclusion rules in Collection accounts editor
Port a40b07164019ee38290eb2167b648a9f46c2634a to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
fe869936ab
commit
8257bdcbca
@ -4,11 +4,8 @@ import { FormattedMessage, useIntl } from 'react-intl';
|
|||||||
|
|
||||||
import { useHistory } from 'react-router-dom';
|
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 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 { Avatar } from 'flavours/glitch/components/avatar';
|
||||||
import { Button } from 'flavours/glitch/components/button';
|
import { Button } from 'flavours/glitch/components/button';
|
||||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||||
@ -24,19 +21,18 @@ import {
|
|||||||
} from 'flavours/glitch/components/scrollable_list/components';
|
} from 'flavours/glitch/components/scrollable_list/components';
|
||||||
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
import { useAccount } from 'flavours/glitch/hooks/useAccount';
|
||||||
import { useSearchAccounts } from 'flavours/glitch/hooks/useSearchAccounts';
|
import { useSearchAccounts } from 'flavours/glitch/hooks/useSearchAccounts';
|
||||||
import { me } from 'flavours/glitch/initial_state';
|
|
||||||
import {
|
import {
|
||||||
addCollectionItem,
|
addCollectionItem,
|
||||||
getCollectionItemIds,
|
getCollectionItemIds,
|
||||||
removeCollectionItem,
|
removeCollectionItem,
|
||||||
updateCollectionEditorField,
|
updateCollectionEditorField,
|
||||||
} from 'flavours/glitch/reducers/slices/collections';
|
} 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 classes from './styles.module.scss';
|
||||||
import { WizardStepTitle } from './wizard_step_title';
|
import { WizardStepTitle } from './wizard_step_title';
|
||||||
|
|
||||||
const MAX_ACCOUNT_COUNT = 3;
|
const MAX_ACCOUNT_COUNT = 25;
|
||||||
|
|
||||||
const AddedAccountItem: React.FC<{
|
const AddedAccountItem: React.FC<{
|
||||||
accountId: string;
|
accountId: string;
|
||||||
@ -46,20 +42,24 @@ const AddedAccountItem: React.FC<{
|
|||||||
onRemove(accountId);
|
onRemove(accountId);
|
||||||
}, [accountId, onRemove]);
|
}, [accountId, onRemove]);
|
||||||
|
|
||||||
return (
|
const renderButton = useCallback(
|
||||||
<Account minimal key={accountId} id={accountId}>
|
() => (
|
||||||
<Button compact secondary onClick={handleRemoveAccount}>
|
<Button compact secondary onClick={handleRemoveAccount}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='collections.remove_account'
|
id='collections.remove_account'
|
||||||
defaultMessage='Remove'
|
defaultMessage='Remove'
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</Account>
|
),
|
||||||
|
[handleRemoveAccount],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return <AccountListItem accountId={accountId} renderButton={renderButton} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SuggestionItem {
|
interface SuggestionItem {
|
||||||
id: string;
|
id: string;
|
||||||
|
isDisabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SuggestedAccountItem: React.FC<SuggestionItem> = ({ id }) => {
|
const SuggestedAccountItem: React.FC<SuggestionItem> = ({ id }) => {
|
||||||
@ -80,6 +80,7 @@ const renderAccountItem = (item: SuggestionItem) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const getItemId = (item: SuggestionItem) => item.id;
|
const getItemId = (item: SuggestionItem) => item.id;
|
||||||
|
const getIsItemDisabled = (item: SuggestionItem) => item.isDisabled ?? false;
|
||||||
|
|
||||||
export const CollectionAccounts: React.FC<{
|
export const CollectionAccounts: React.FC<{
|
||||||
collection?: ApiCollectionJSON | null;
|
collection?: ApiCollectionJSON | null;
|
||||||
@ -109,21 +110,22 @@ export const CollectionAccounts: React.FC<{
|
|||||||
const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT;
|
const hasMaxAccounts = accountIds.length === MAX_ACCOUNT_COUNT;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
accountIds: suggestedAccountIds,
|
accounts: suggestedAccounts,
|
||||||
isLoading: isLoadingSuggestions,
|
isLoading: isLoadingSuggestions,
|
||||||
searchAccounts,
|
searchAccounts,
|
||||||
resetAccounts,
|
resetAccounts,
|
||||||
} = useSearchAccounts({
|
} = useSearchAccounts({
|
||||||
withRelationships: true,
|
withRelationships: true,
|
||||||
filterResults: (account) =>
|
// Don't suggest accounts that were already added
|
||||||
!accountIds.includes(account.id) &&
|
filterResults: (account) => !accountIds.includes(account.id),
|
||||||
// Only suggest accounts who allow being featured/recommended
|
|
||||||
account.feature_approval.current_user === 'automatic',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const suggestedItems = suggestedAccountIds.map((id) => ({
|
const suggestedItems = suggestedAccounts.map(({ id, feature_approval }) => ({
|
||||||
id,
|
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(
|
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(
|
const removeAccountItem = useCallback(
|
||||||
(accountId: string) => {
|
(accountId: string) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
@ -194,16 +159,14 @@ export const CollectionAccounts: React.FC<{
|
|||||||
|
|
||||||
const addAccountItem = useCallback(
|
const addAccountItem = useCallback(
|
||||||
(item: SuggestionItem) => {
|
(item: SuggestionItem) => {
|
||||||
confirmFollowStatus(item.id, () => {
|
dispatch(
|
||||||
dispatch(
|
updateCollectionEditorField({
|
||||||
updateCollectionEditorField({
|
field: 'accountIds',
|
||||||
field: 'accountIds',
|
value: [...accountIds, item.id],
|
||||||
value: [...accountIds, item.id],
|
}),
|
||||||
}),
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[accountIds, confirmFollowStatus, dispatch],
|
[accountIds, dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const instantRemoveAccountItem = useCallback(
|
const instantRemoveAccountItem = useCallback(
|
||||||
@ -230,15 +193,13 @@ export const CollectionAccounts: React.FC<{
|
|||||||
|
|
||||||
const instantAddAccountItem = useCallback(
|
const instantAddAccountItem = useCallback(
|
||||||
(item: SuggestionItem) => {
|
(item: SuggestionItem) => {
|
||||||
confirmFollowStatus(item.id, () => {
|
if (id) {
|
||||||
if (id) {
|
void dispatch(
|
||||||
void dispatch(
|
addCollectionItem({ collectionId: id, accountId: item.id }),
|
||||||
addCollectionItem({ collectionId: id, accountId: item.id }),
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[confirmFollowStatus, dispatch, id],
|
[dispatch, id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRemoveAccountItem = useCallback(
|
const handleRemoveAccountItem = useCallback(
|
||||||
@ -310,6 +271,7 @@ export const CollectionAccounts: React.FC<{
|
|||||||
isLoading={isLoadingSuggestions}
|
isLoading={isLoadingSuggestions}
|
||||||
items={suggestedItems}
|
items={suggestedItems}
|
||||||
getItemId={getItemId}
|
getItemId={getItemId}
|
||||||
|
getIsItemDisabled={getIsItemDisabled}
|
||||||
renderItem={renderAccountItem}
|
renderItem={renderAccountItem}
|
||||||
onSelectItem={handleSelectItem}
|
onSelectItem={handleSelectItem}
|
||||||
status={
|
status={
|
||||||
|
|||||||
@ -164,7 +164,7 @@ const ListMembers: React.FC<{
|
|||||||
const [mode, setMode] = useState<Mode>('remove');
|
const [mode, setMode] = useState<Mode>('remove');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
accountIds: searchAccountIds,
|
accounts: accountsFromSearch,
|
||||||
isLoading: loadingSearchResults,
|
isLoading: loadingSearchResults,
|
||||||
searchAccounts: handleSearch,
|
searchAccounts: handleSearch,
|
||||||
} = useSearchAccounts({
|
} = useSearchAccounts({
|
||||||
@ -177,6 +177,7 @@ const ListMembers: React.FC<{
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const accountIdsFromSearch = accountsFromSearch.map((item) => item.id);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
@ -220,7 +221,7 @@ const ListMembers: React.FC<{
|
|||||||
let displayedAccountIds: string[];
|
let displayedAccountIds: string[];
|
||||||
|
|
||||||
if (mode === 'add' && searching) {
|
if (mode === 'add' && searching) {
|
||||||
displayedAccountIds = searchAccountIds;
|
displayedAccountIds = accountIdsFromSearch;
|
||||||
} else {
|
} else {
|
||||||
displayedAccountIds = accountIds;
|
displayedAccountIds = accountIds;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 (
|
|
||||||
<ConfirmationModal
|
|
||||||
title={intl.formatMessage(messages.title)}
|
|
||||||
message={
|
|
||||||
<FormattedMessage
|
|
||||||
id='confirmations.follow_to_collection.message'
|
|
||||||
defaultMessage='You need to be following {name} to add them to a collection.'
|
|
||||||
values={{ name: <strong>@{account?.acct}</strong> }}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
confirm={intl.formatMessage(messages.confirm)}
|
|
||||||
onConfirm={onConfirm}
|
|
||||||
onClose={onClose}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -13,7 +13,6 @@ export { ConfirmUnblockModal } from './unblock';
|
|||||||
export { ConfirmClearNotificationsModal } from './clear_notifications';
|
export { ConfirmClearNotificationsModal } from './clear_notifications';
|
||||||
export { ConfirmLogOutModal } from './log_out';
|
export { ConfirmLogOutModal } from './log_out';
|
||||||
export { ConfirmFollowToListModal } from './follow_to_list';
|
export { ConfirmFollowToListModal } from './follow_to_list';
|
||||||
export { ConfirmFollowToCollectionModal } from './follow_to_collection';
|
|
||||||
export { ConfirmMissingAltTextModal } from './missing_alt_text';
|
export { ConfirmMissingAltTextModal } from './missing_alt_text';
|
||||||
export { ConfirmRevokeQuoteModal } from './revoke_quote';
|
export { ConfirmRevokeQuoteModal } from './revoke_quote';
|
||||||
export { QuietPostQuoteInfoModal } from './quiet_post_quote_info';
|
export { QuietPostQuoteInfoModal } from './quiet_post_quote_info';
|
||||||
|
|||||||
@ -40,7 +40,6 @@ import {
|
|||||||
ConfirmClearNotificationsModal,
|
ConfirmClearNotificationsModal,
|
||||||
ConfirmLogOutModal,
|
ConfirmLogOutModal,
|
||||||
ConfirmFollowToListModal,
|
ConfirmFollowToListModal,
|
||||||
ConfirmFollowToCollectionModal,
|
|
||||||
ConfirmMissingAltTextModal,
|
ConfirmMissingAltTextModal,
|
||||||
ConfirmRevokeQuoteModal,
|
ConfirmRevokeQuoteModal,
|
||||||
QuietPostQuoteInfoModal,
|
QuietPostQuoteInfoModal,
|
||||||
@ -75,7 +74,6 @@ export const MODAL_COMPONENTS = {
|
|||||||
'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }),
|
'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }),
|
||||||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
'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_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||||
'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }),
|
'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }),
|
||||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export function useSearchAccounts({
|
|||||||
} = {}) {
|
} = {}) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const [accountIds, setAccountIds] = useState<string[]>([]);
|
const [accounts, setAccounts] = useState<ApiAccountJSON[]>([]);
|
||||||
const [loadingState, setLoadingState] = useState<
|
const [loadingState, setLoadingState] = useState<
|
||||||
'idle' | 'loading' | 'error'
|
'idle' | 'loading' | 'error'
|
||||||
>('idle');
|
>('idle');
|
||||||
@ -37,7 +37,7 @@ export function useSearchAccounts({
|
|||||||
if (value.trim().length === 0) {
|
if (value.trim().length === 0) {
|
||||||
onSettled?.('');
|
onSettled?.('');
|
||||||
if (resetOnInputClear) {
|
if (resetOnInputClear) {
|
||||||
setAccountIds([]);
|
setAccounts([]);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ export function useSearchAccounts({
|
|||||||
if (withRelationships) {
|
if (withRelationships) {
|
||||||
dispatch(fetchRelationships(accountIds));
|
dispatch(fetchRelationships(accountIds));
|
||||||
}
|
}
|
||||||
setAccountIds(accountIds);
|
setAccounts(accounts);
|
||||||
setLoadingState('idle');
|
setLoadingState('idle');
|
||||||
onSettled?.(value);
|
onSettled?.(value);
|
||||||
})
|
})
|
||||||
@ -74,13 +74,13 @@ export function useSearchAccounts({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const resetAccounts = useCallback(() => {
|
const resetAccounts = useCallback(() => {
|
||||||
setAccountIds([]);
|
setAccounts([]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
searchAccounts,
|
searchAccounts,
|
||||||
resetAccounts,
|
resetAccounts,
|
||||||
accountIds,
|
accounts,
|
||||||
isLoading: loadingState === 'loading',
|
isLoading: loadingState === 'loading',
|
||||||
isError: loadingState === 'error',
|
isError: loadingState === 'error',
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user