From 31e5f860d919bfcb7ae572fae48bb01d80a7777a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 21 Jul 2023 12:36:02 -0500 Subject: [PATCH] Add useSuggest hook https://gitlab.com/soapbox-pub/soapbox/-/issues/1483 --- app/soapbox/actions/admin.ts | 42 ----------- app/soapbox/api/hooks/admin/useSuggest.ts | 71 +++++++++++++++++++ .../entity-store/hooks/useEntityLookup.ts | 14 +--- app/soapbox/entity-store/selectors.ts | 14 ++++ .../account-moderation-modal.tsx | 12 ++-- app/soapbox/reducers/accounts.ts | 18 ----- 6 files changed, 92 insertions(+), 79 deletions(-) create mode 100644 app/soapbox/api/hooks/admin/useSuggest.ts diff --git a/app/soapbox/actions/admin.ts b/app/soapbox/actions/admin.ts index d17716be9..a7f708d83 100644 --- a/app/soapbox/actions/admin.ts +++ b/app/soapbox/actions/admin.ts @@ -75,14 +75,6 @@ const ADMIN_REMOVE_PERMISSION_GROUP_REQUEST = 'ADMIN_REMOVE_PERMISSION_GROUP_REQ const ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS = 'ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS'; const ADMIN_REMOVE_PERMISSION_GROUP_FAIL = 'ADMIN_REMOVE_PERMISSION_GROUP_FAIL'; -const ADMIN_USERS_SUGGEST_REQUEST = 'ADMIN_USERS_SUGGEST_REQUEST'; -const ADMIN_USERS_SUGGEST_SUCCESS = 'ADMIN_USERS_SUGGEST_SUCCESS'; -const ADMIN_USERS_SUGGEST_FAIL = 'ADMIN_USERS_SUGGEST_FAIL'; - -const ADMIN_USERS_UNSUGGEST_REQUEST = 'ADMIN_USERS_UNSUGGEST_REQUEST'; -const ADMIN_USERS_UNSUGGEST_SUCCESS = 'ADMIN_USERS_UNSUGGEST_SUCCESS'; -const ADMIN_USERS_UNSUGGEST_FAIL = 'ADMIN_USERS_UNSUGGEST_FAIL'; - const ADMIN_USER_INDEX_EXPAND_FAIL = 'ADMIN_USER_INDEX_EXPAND_FAIL'; const ADMIN_USER_INDEX_EXPAND_REQUEST = 'ADMIN_USER_INDEX_EXPAND_REQUEST'; const ADMIN_USER_INDEX_EXPAND_SUCCESS = 'ADMIN_USER_INDEX_EXPAND_SUCCESS'; @@ -563,32 +555,6 @@ const setRole = (accountId: string, role: 'user' | 'moderator' | 'admin') => } }; -const suggestUsers = (accountIds: string[]) => - (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); - dispatch({ type: ADMIN_USERS_SUGGEST_REQUEST, accountIds }); - return api(getState) - .patch('/api/v1/pleroma/admin/users/suggest', { nicknames }) - .then(({ data: { users } }) => { - dispatch({ type: ADMIN_USERS_SUGGEST_SUCCESS, users, accountIds }); - }).catch(error => { - dispatch({ type: ADMIN_USERS_SUGGEST_FAIL, error, accountIds }); - }); - }; - -const unsuggestUsers = (accountIds: string[]) => - (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); - dispatch({ type: ADMIN_USERS_UNSUGGEST_REQUEST, accountIds }); - return api(getState) - .patch('/api/v1/pleroma/admin/users/unsuggest', { nicknames }) - .then(({ data: { users } }) => { - dispatch({ type: ADMIN_USERS_UNSUGGEST_SUCCESS, users, accountIds }); - }).catch(error => { - dispatch({ type: ADMIN_USERS_UNSUGGEST_FAIL, error, accountIds }); - }); - }; - const setUserIndexQuery = (query: string) => ({ type: ADMIN_USER_INDEX_QUERY_SET, query }); const fetchUserIndex = () => @@ -766,12 +732,6 @@ export { ADMIN_REMOVE_PERMISSION_GROUP_REQUEST, ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS, ADMIN_REMOVE_PERMISSION_GROUP_FAIL, - ADMIN_USERS_SUGGEST_REQUEST, - ADMIN_USERS_SUGGEST_SUCCESS, - ADMIN_USERS_SUGGEST_FAIL, - ADMIN_USERS_UNSUGGEST_REQUEST, - ADMIN_USERS_UNSUGGEST_SUCCESS, - ADMIN_USERS_UNSUGGEST_FAIL, ADMIN_USER_INDEX_EXPAND_FAIL, ADMIN_USER_INDEX_EXPAND_REQUEST, ADMIN_USER_INDEX_EXPAND_SUCCESS, @@ -820,8 +780,6 @@ export { promoteToModerator, demoteToUser, setRole, - suggestUsers, - unsuggestUsers, setUserIndexQuery, fetchUserIndex, expandUserIndex, diff --git a/app/soapbox/api/hooks/admin/useSuggest.ts b/app/soapbox/api/hooks/admin/useSuggest.ts new file mode 100644 index 000000000..2948beba7 --- /dev/null +++ b/app/soapbox/api/hooks/admin/useSuggest.ts @@ -0,0 +1,71 @@ +import { Entities } from 'soapbox/entity-store/entities'; +import { useTransaction } from 'soapbox/entity-store/hooks'; +import { EntityCallbacks } from 'soapbox/entity-store/hooks/types'; +import { findEntity } from 'soapbox/entity-store/selectors'; +import { useApi, useGetState } from 'soapbox/hooks'; + +import type { Account } from 'soapbox/schemas'; +import type { RootState } from 'soapbox/store'; + +function useSuggest() { + const api = useApi(); + const getState = useGetState(); + const { transaction } = useTransaction(); + + function suggestEffect(accts: string[], suggested: boolean) { + const ids = selectIdsForAccts(getState(), accts); + + const updater = (account: Account): Account => { + if (account.pleroma) { + account.pleroma.is_suggested = suggested; + } + return account; + }; + + transaction({ + Accounts: ids.reduce Account>>( + (result, id) => ({ ...result, [id]: updater }), + {}), + }); + } + + async function suggest(accts: string[], callbacks?: EntityCallbacks) { + suggestEffect(accts, true); + try { + await api.patch('/api/v1/pleroma/admin/users/suggest', { nicknames: accts }); + callbacks?.onSuccess?.(); + } catch (e) { + callbacks?.onError?.(e); + suggestEffect(accts, false); + } + } + + async function unsuggest(accts: string[], callbacks?: EntityCallbacks) { + suggestEffect(accts, false); + try { + await api.patch('/api/v1/pleroma/admin/users/unsuggest', { nicknames: accts }); + callbacks?.onSuccess?.(); + } catch (e) { + callbacks?.onError?.(e); + suggestEffect(accts, true); + } + } + + return { + suggest, + unsuggest, + }; +} + +function selectIdsForAccts(state: RootState, accts: string[]): string[] { + return accts.map((acct) => { + const account = findEntity( + state, + Entities.ACCOUNTS, + (account) => account.acct === acct, + ); + return account!.id; + }); +} + +export { useSuggest }; \ No newline at end of file diff --git a/app/soapbox/entity-store/hooks/useEntityLookup.ts b/app/soapbox/entity-store/hooks/useEntityLookup.ts index 29cf85244..1a8a11eda 100644 --- a/app/soapbox/entity-store/hooks/useEntityLookup.ts +++ b/app/soapbox/entity-store/hooks/useEntityLookup.ts @@ -3,9 +3,9 @@ import { useEffect, useState } from 'react'; import { z } from 'zod'; import { useAppDispatch, useAppSelector, useLoading } from 'soapbox/hooks'; -import { type RootState } from 'soapbox/store'; import { importEntities } from '../actions'; +import { findEntity } from '../selectors'; import { Entity } from '../types'; import { EntityFn } from './types'; @@ -58,16 +58,4 @@ function useEntityLookup( }; } -function findEntity( - state: RootState, - entityType: string, - lookupFn: LookupFn, -) { - const cache = state.entities[entityType]; - - if (cache) { - return (Object.values(cache.store) as TEntity[]).find(lookupFn); - } -} - export { useEntityLookup }; \ No newline at end of file diff --git a/app/soapbox/entity-store/selectors.ts b/app/soapbox/entity-store/selectors.ts index ac5f3feff..d1017c5b6 100644 --- a/app/soapbox/entity-store/selectors.ts +++ b/app/soapbox/entity-store/selectors.ts @@ -44,10 +44,24 @@ function selectEntities(state: RootState, path: Entities ) : []; } +/** Find an entity using a finder function. */ +function findEntity( + state: RootState, + entityType: string, + lookupFn: (entity: TEntity) => boolean, +) { + const cache = state.entities[entityType]; + + if (cache) { + return (Object.values(cache.store) as TEntity[]).find(lookupFn); + } +} + export { selectCache, selectList, selectListState, useListState, selectEntities, + findEntity, }; \ No newline at end of file diff --git a/app/soapbox/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx b/app/soapbox/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx index 016759205..9d837f5cf 100644 --- a/app/soapbox/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx +++ b/app/soapbox/features/ui/components/modals/account-moderation-modal/account-moderation-modal.tsx @@ -4,12 +4,11 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { verifyUser, unverifyUser, - suggestUsers, - unsuggestUsers, setBadges as saveBadges, } from 'soapbox/actions/admin'; import { deactivateUserModal, deleteUserModal } from 'soapbox/actions/moderation'; import { useAccount } from 'soapbox/api/hooks'; +import { useSuggest } from 'soapbox/api/hooks/admin/useSuggest'; import Account from 'soapbox/components/account'; import List, { ListItem } from 'soapbox/components/list'; import MissingIndicator from 'soapbox/components/missing-indicator'; @@ -45,6 +44,7 @@ const AccountModerationModal: React.FC = ({ onClose, ac const intl = useIntl(); const dispatch = useAppDispatch(); + const { suggest, unsuggest } = useSuggest(); const { account: ownAccount } = useOwnAccount(); const features = useFeatures(); const { account } = useAccount(accountId); @@ -81,11 +81,11 @@ const AccountModerationModal: React.FC = ({ onClose, ac const { checked } = e.target; const message = checked ? messages.userSuggested : messages.userUnsuggested; - const action = checked ? suggestUsers : unsuggestUsers; + const action = checked ? suggest : unsuggest; - dispatch(action([account.id])) - .then(() => toast.success(intl.formatMessage(message, { acct: account.acct }))) - .catch(() => {}); + action([account.acct], { + onSuccess: () => toast.success(intl.formatMessage(message, { acct: account.acct })), + }); }; const handleDeactivate = () => { diff --git a/app/soapbox/reducers/accounts.ts b/app/soapbox/reducers/accounts.ts index 39176e731..5587e0a73 100644 --- a/app/soapbox/reducers/accounts.ts +++ b/app/soapbox/reducers/accounts.ts @@ -23,10 +23,6 @@ import { ADMIN_USERS_DELETE_FAIL, ADMIN_USERS_DEACTIVATE_REQUEST, ADMIN_USERS_DEACTIVATE_FAIL, - ADMIN_USERS_SUGGEST_REQUEST, - ADMIN_USERS_SUGGEST_FAIL, - ADMIN_USERS_UNSUGGEST_REQUEST, - ADMIN_USERS_UNSUGGEST_FAIL, } from 'soapbox/actions/admin'; import { CHATS_FETCH_SUCCESS, CHATS_EXPAND_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats'; import { @@ -234,14 +230,6 @@ const importAdminUsers = (state: State, adminUsers: Array>): }); }; -const setSuggested = (state: State, accountIds: Array, isSuggested: boolean): State => { - return state.withMutations(state => { - accountIds.forEach(id => { - state.setIn([id, 'pleroma', 'is_suggested'], isSuggested); - }); - }); -}; - export default function accounts(state: State = initialState, action: AnyAction): State { switch (action.type) { case ACCOUNT_IMPORT: @@ -280,12 +268,6 @@ export default function accounts(state: State = initialState, action: AnyAction) return setActive(state, action.accountIds, true); case ADMIN_USERS_FETCH_SUCCESS: return importAdminUsers(state, action.users); - case ADMIN_USERS_SUGGEST_REQUEST: - case ADMIN_USERS_UNSUGGEST_FAIL: - return setSuggested(state, action.accountIds, true); - case ADMIN_USERS_UNSUGGEST_REQUEST: - case ADMIN_USERS_SUGGEST_FAIL: - return setSuggested(state, action.accountIds, false); default: return state; }