From 31e5f860d919bfcb7ae572fae48bb01d80a7777a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 21 Jul 2023 12:36:02 -0500 Subject: [PATCH 1/5] 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; } From 99e8f6912d40aa4ef6fabae6248847a6d780f476 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 21 Jul 2023 12:48:47 -0500 Subject: [PATCH 2/5] Add useVerify hook --- app/soapbox/actions/admin.ts | 10 --- app/soapbox/api/hooks/admin/useVerify.ts | 76 +++++++++++++++++++ .../account-moderation-modal.tsx | 16 ++-- 3 files changed, 83 insertions(+), 19 deletions(-) create mode 100644 app/soapbox/api/hooks/admin/useVerify.ts diff --git a/app/soapbox/actions/admin.ts b/app/soapbox/actions/admin.ts index a7f708d83..0ab2b827f 100644 --- a/app/soapbox/actions/admin.ts +++ b/app/soapbox/actions/admin.ts @@ -488,14 +488,6 @@ const setBadges = (accountId: string, oldTags: string[], newTags: string[]) => return dispatch(setTags(accountId, oldBadges, newBadges)); }; -const verifyUser = (accountId: string) => - (dispatch: AppDispatch) => - dispatch(tagUsers([accountId], ['verified'])); - -const unverifyUser = (accountId: string) => - (dispatch: AppDispatch) => - dispatch(untagUsers([accountId], ['verified'])); - const addPermission = (accountIds: string[], permissionGroup: string) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = nicknamesFromIds(getState, accountIds); @@ -772,8 +764,6 @@ export { untagUsers, setTags, setBadges, - verifyUser, - unverifyUser, addPermission, removePermission, promoteToAdmin, diff --git a/app/soapbox/api/hooks/admin/useVerify.ts b/app/soapbox/api/hooks/admin/useVerify.ts new file mode 100644 index 000000000..adee278b6 --- /dev/null +++ b/app/soapbox/api/hooks/admin/useVerify.ts @@ -0,0 +1,76 @@ +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 useVerify() { + const api = useApi(); + const getState = useGetState(); + const { transaction } = useTransaction(); + + function verifyEffect(accts: string[], verified: boolean) { + const ids = selectIdsForAccts(getState(), accts); + + const updater = (account: Account): Account => { + if (account.pleroma) { + const tags = account.pleroma.tags.filter((tag) => tag !== 'verified'); + if (verified) { + tags.push('verified'); + } + account.pleroma.tags = tags; + } + account.verified = verified; + return account; + }; + + transaction({ + Accounts: ids.reduce Account>>( + (result, id) => ({ ...result, [id]: updater }), + {}), + }); + } + + async function verify(accts: string[], callbacks?: EntityCallbacks) { + verifyEffect(accts, true); + try { + await api.put('/api/v1/pleroma/admin/users/tag', { nicknames: accts, tags: ['verified'] }); + callbacks?.onSuccess?.(); + } catch (e) { + callbacks?.onError?.(e); + verifyEffect(accts, false); + } + } + + async function unverify(accts: string[], callbacks?: EntityCallbacks) { + verifyEffect(accts, false); + try { + await api.delete('/api/v1/pleroma/admin/users/tag', { data: { nicknames: accts, tags: ['verified'] } }); + callbacks?.onSuccess?.(); + } catch (e) { + callbacks?.onError?.(e); + verifyEffect(accts, true); + } + } + + return { + verify, + unverify, + }; +} + +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 { useVerify }; \ 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 9d837f5cf..6255a11f6 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 @@ -1,14 +1,11 @@ import React, { ChangeEventHandler, useState } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import { - verifyUser, - unverifyUser, - setBadges as saveBadges, -} from 'soapbox/actions/admin'; +import { 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 { useVerify } from 'soapbox/api/hooks/admin/useVerify'; import Account from 'soapbox/components/account'; import List, { ListItem } from 'soapbox/components/list'; import MissingIndicator from 'soapbox/components/missing-indicator'; @@ -45,6 +42,7 @@ const AccountModerationModal: React.FC = ({ onClose, ac const dispatch = useAppDispatch(); const { suggest, unsuggest } = useSuggest(); + const { verify, unverify } = useVerify(); const { account: ownAccount } = useOwnAccount(); const features = useFeatures(); const { account } = useAccount(accountId); @@ -70,11 +68,11 @@ const AccountModerationModal: React.FC = ({ onClose, ac const { checked } = e.target; const message = checked ? messages.userVerified : messages.userUnverified; - const action = checked ? verifyUser : unverifyUser; + const action = checked ? verify : unverify; - 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 handleSuggestedChange: ChangeEventHandler = (e) => { From d0a97c8c52e47872501e745488a125795d53025a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 21 Jul 2023 12:49:54 -0500 Subject: [PATCH 3/5] Export admin API hooks from an index.ts --- app/soapbox/api/hooks/admin/index.ts | 2 ++ .../account-moderation-modal/account-moderation-modal.tsx | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 app/soapbox/api/hooks/admin/index.ts diff --git a/app/soapbox/api/hooks/admin/index.ts b/app/soapbox/api/hooks/admin/index.ts new file mode 100644 index 000000000..ef4dc082d --- /dev/null +++ b/app/soapbox/api/hooks/admin/index.ts @@ -0,0 +1,2 @@ +export { useSuggest } from './useSuggest'; +export { useVerify } from './useVerify'; \ 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 6255a11f6..a336e2d7e 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,8 +4,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { 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 { useVerify } from 'soapbox/api/hooks/admin/useVerify'; +import { useSuggest, useVerify } from 'soapbox/api/hooks/admin'; import Account from 'soapbox/components/account'; import List, { ListItem } from 'soapbox/components/list'; import MissingIndicator from 'soapbox/components/missing-indicator'; From 40af1d91a46cd0ff7622db501b9a434edaa4756b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 21 Jul 2023 12:59:31 -0500 Subject: [PATCH 4/5] suggest and verify by account IDs, simplify hooks --- app/soapbox/actions/admin.ts | 18 +++++----- app/soapbox/api/hooks/admin/useSuggest.ts | 35 ++++++------------- app/soapbox/api/hooks/admin/useVerify.ts | 35 ++++++------------- .../account-moderation-modal.tsx | 4 +-- app/soapbox/selectors/index.ts | 2 ++ 5 files changed, 34 insertions(+), 60 deletions(-) diff --git a/app/soapbox/actions/admin.ts b/app/soapbox/actions/admin.ts index 0ab2b827f..613615140 100644 --- a/app/soapbox/actions/admin.ts +++ b/app/soapbox/actions/admin.ts @@ -2,7 +2,7 @@ import { defineMessages } from 'react-intl'; import { fetchRelationships } from 'soapbox/actions/accounts'; import { importFetchedAccount, importFetchedAccounts, importFetchedStatuses } from 'soapbox/actions/importer'; -import { selectAccount } from 'soapbox/selectors'; +import { accountIdsToAccts } from 'soapbox/selectors'; import toast from 'soapbox/toast'; import { filterBadges, getTagDiff } from 'soapbox/utils/badges'; import { getFeatures } from 'soapbox/utils/features'; @@ -114,8 +114,6 @@ const messages = defineMessages({ announcementUpdateSuccess: { id: 'admin.edit_announcement.updated', defaultMessage: 'Announcement edited' }, }); -const nicknamesFromIds = (getState: () => RootState, ids: string[]) => ids.map((id) => selectAccount(getState(), id)!.acct); - const fetchConfig = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: ADMIN_CONFIG_FETCH_REQUEST }); @@ -322,7 +320,7 @@ const deactivateMastodonUsers = (accountIds: string[], reportId?: string) => const deactivatePleromaUsers = (accountIds: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); + const nicknames = accountIdsToAccts(getState(), accountIds); return api(getState) .patch('/api/v1/pleroma/admin/users/deactivate', { nicknames }) .then(({ data: { users } }) => { @@ -350,7 +348,7 @@ const deactivateUsers = (accountIds: string[], reportId?: string) => const deleteUsers = (accountIds: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); + const nicknames = accountIdsToAccts(getState(), accountIds); dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountIds }); return api(getState) .delete('/api/v1/pleroma/admin/users', { data: { nicknames } }) @@ -375,7 +373,7 @@ const approveMastodonUsers = (accountIds: string[]) => const approvePleromaUsers = (accountIds: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); + const nicknames = accountIdsToAccts(getState(), accountIds); return api(getState) .patch('/api/v1/pleroma/admin/users/approve', { nicknames }) .then(({ data: { users } }) => { @@ -440,7 +438,7 @@ const fetchModerationLog = (params?: Record) => const tagUsers = (accountIds: string[], tags: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); + const nicknames = accountIdsToAccts(getState(), accountIds); dispatch({ type: ADMIN_USERS_TAG_REQUEST, accountIds, tags }); return api(getState) .put('/api/v1/pleroma/admin/users/tag', { nicknames, tags }) @@ -453,7 +451,7 @@ const tagUsers = (accountIds: string[], tags: string[]) => const untagUsers = (accountIds: string[], tags: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); + const nicknames = accountIdsToAccts(getState(), accountIds); // Legacy: allow removing legacy 'donor' tags. if (tags.includes('badge:donor')) { @@ -490,7 +488,7 @@ const setBadges = (accountId: string, oldTags: string[], newTags: string[]) => const addPermission = (accountIds: string[], permissionGroup: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); + const nicknames = accountIdsToAccts(getState(), accountIds); dispatch({ type: ADMIN_ADD_PERMISSION_GROUP_REQUEST, accountIds, permissionGroup }); return api(getState) .post(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { nicknames }) @@ -503,7 +501,7 @@ const addPermission = (accountIds: string[], permissionGroup: string) => const removePermission = (accountIds: string[], permissionGroup: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = nicknamesFromIds(getState, accountIds); + const nicknames = accountIdsToAccts(getState(), accountIds); dispatch({ type: ADMIN_REMOVE_PERMISSION_GROUP_REQUEST, accountIds, permissionGroup }); return api(getState) .delete(`/api/v1/pleroma/admin/users/permission_group/${permissionGroup}`, { data: { nicknames } }) diff --git a/app/soapbox/api/hooks/admin/useSuggest.ts b/app/soapbox/api/hooks/admin/useSuggest.ts index 2948beba7..b20bc5308 100644 --- a/app/soapbox/api/hooks/admin/useSuggest.ts +++ b/app/soapbox/api/hooks/admin/useSuggest.ts @@ -1,20 +1,16 @@ -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 { accountIdsToAccts } from 'soapbox/selectors'; 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); - + function suggestEffect(accountIds: string[], suggested: boolean) { const updater = (account: Account): Account => { if (account.pleroma) { account.pleroma.is_suggested = suggested; @@ -23,31 +19,33 @@ function useSuggest() { }; transaction({ - Accounts: ids.reduce Account>>( + Accounts: accountIds.reduce Account>>( (result, id) => ({ ...result, [id]: updater }), {}), }); } - async function suggest(accts: string[], callbacks?: EntityCallbacks) { - suggestEffect(accts, true); + async function suggest(accountIds: string[], callbacks?: EntityCallbacks) { + const accts = accountIdsToAccts(getState(), accountIds); + suggestEffect(accountIds, true); try { await api.patch('/api/v1/pleroma/admin/users/suggest', { nicknames: accts }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); - suggestEffect(accts, false); + suggestEffect(accountIds, false); } } - async function unsuggest(accts: string[], callbacks?: EntityCallbacks) { - suggestEffect(accts, false); + async function unsuggest(accountIds: string[], callbacks?: EntityCallbacks) { + const accts = accountIdsToAccts(getState(), accountIds); + suggestEffect(accountIds, false); try { await api.patch('/api/v1/pleroma/admin/users/unsuggest', { nicknames: accts }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); - suggestEffect(accts, true); + suggestEffect(accountIds, true); } } @@ -57,15 +55,4 @@ function useSuggest() { }; } -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/api/hooks/admin/useVerify.ts b/app/soapbox/api/hooks/admin/useVerify.ts index adee278b6..090e1bc43 100644 --- a/app/soapbox/api/hooks/admin/useVerify.ts +++ b/app/soapbox/api/hooks/admin/useVerify.ts @@ -1,20 +1,16 @@ -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 { accountIdsToAccts } from 'soapbox/selectors'; import type { Account } from 'soapbox/schemas'; -import type { RootState } from 'soapbox/store'; function useVerify() { const api = useApi(); const getState = useGetState(); const { transaction } = useTransaction(); - function verifyEffect(accts: string[], verified: boolean) { - const ids = selectIdsForAccts(getState(), accts); - + function verifyEffect(accountIds: string[], verified: boolean) { const updater = (account: Account): Account => { if (account.pleroma) { const tags = account.pleroma.tags.filter((tag) => tag !== 'verified'); @@ -28,31 +24,33 @@ function useVerify() { }; transaction({ - Accounts: ids.reduce Account>>( + Accounts: accountIds.reduce Account>>( (result, id) => ({ ...result, [id]: updater }), {}), }); } - async function verify(accts: string[], callbacks?: EntityCallbacks) { - verifyEffect(accts, true); + async function verify(accountIds: string[], callbacks?: EntityCallbacks) { + const accts = accountIdsToAccts(getState(), accountIds); + verifyEffect(accountIds, true); try { await api.put('/api/v1/pleroma/admin/users/tag', { nicknames: accts, tags: ['verified'] }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); - verifyEffect(accts, false); + verifyEffect(accountIds, false); } } - async function unverify(accts: string[], callbacks?: EntityCallbacks) { - verifyEffect(accts, false); + async function unverify(accountIds: string[], callbacks?: EntityCallbacks) { + const accts = accountIdsToAccts(getState(), accountIds); + verifyEffect(accountIds, false); try { await api.delete('/api/v1/pleroma/admin/users/tag', { data: { nicknames: accts, tags: ['verified'] } }); callbacks?.onSuccess?.(); } catch (e) { callbacks?.onError?.(e); - verifyEffect(accts, true); + verifyEffect(accountIds, true); } } @@ -62,15 +60,4 @@ function useVerify() { }; } -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 { useVerify }; \ 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 a336e2d7e..2d5958f2d 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 @@ -69,7 +69,7 @@ const AccountModerationModal: React.FC = ({ onClose, ac const message = checked ? messages.userVerified : messages.userUnverified; const action = checked ? verify : unverify; - action([account.acct], { + action([account.id], { onSuccess: () => toast.success(intl.formatMessage(message, { acct: account.acct })), }); }; @@ -80,7 +80,7 @@ const AccountModerationModal: React.FC = ({ onClose, ac const message = checked ? messages.userSuggested : messages.userUnsuggested; const action = checked ? suggest : unsuggest; - action([account.acct], { + action([account.id], { onSuccess: () => toast.success(intl.formatMessage(message, { acct: account.acct })), }); }; diff --git a/app/soapbox/selectors/index.ts b/app/soapbox/selectors/index.ts index 4bbf90a42..1f844bc27 100644 --- a/app/soapbox/selectors/index.ts +++ b/app/soapbox/selectors/index.ts @@ -33,6 +33,8 @@ export function selectOwnAccount(state: RootState) { } } +export const accountIdsToAccts = (state: RootState, ids: string[]) => ids.map((id) => selectAccount(state, id)!.acct); + const getAccountBase = (state: RootState, id: string) => state.entities[Entities.ACCOUNTS]?.store[id] as Account | undefined; const getAccountRelationship = (state: RootState, id: string) => state.relationships.get(id); From 8df3470f87a585d4e7d166cdb0063cef7692b29e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 21 Jul 2023 13:11:36 -0500 Subject: [PATCH 5/5] Fix auth test --- app/soapbox/reducers/__tests__/auth.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/soapbox/reducers/__tests__/auth.test.ts b/app/soapbox/reducers/__tests__/auth.test.ts index 9f5726f77..d461b3ddd 100644 --- a/app/soapbox/reducers/__tests__/auth.test.ts +++ b/app/soapbox/reducers/__tests__/auth.test.ts @@ -1,4 +1,4 @@ -import { Map as ImmutableMap, fromJS } from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; import { AUTH_APP_CREATED, @@ -300,7 +300,7 @@ describe('auth reducer', () => { it('sets the value of `me`', () => { const action = { type: SWITCH_ACCOUNT, - account: fromJS({ url: 'https://gleasonator.com/users/benis' }), + account: { url: 'https://gleasonator.com/users/benis' }, }; const result = reducer(undefined, action);