diff --git a/src/actions/admin.ts b/src/actions/admin.ts index 45b5076fb..0bfe1ed3f 100644 --- a/src/actions/admin.ts +++ b/src/actions/admin.ts @@ -39,6 +39,10 @@ const ADMIN_USERS_APPROVE_REQUEST = 'ADMIN_USERS_APPROVE_REQUEST'; const ADMIN_USERS_APPROVE_SUCCESS = 'ADMIN_USERS_APPROVE_SUCCESS'; const ADMIN_USERS_APPROVE_FAIL = 'ADMIN_USERS_APPROVE_FAIL'; +const ADMIN_USERS_REJECT_REQUEST = 'ADMIN_USERS_REJECT_REQUEST'; +const ADMIN_USERS_REJECT_SUCCESS = 'ADMIN_USERS_REJECT_SUCCESS'; +const ADMIN_USERS_REJECT_FAIL = 'ADMIN_USERS_REJECT_FAIL'; + const ADMIN_USERS_DEACTIVATE_REQUEST = 'ADMIN_USERS_DEACTIVATE_REQUEST'; const ADMIN_USERS_DEACTIVATE_SUCCESS = 'ADMIN_USERS_DEACTIVATE_SUCCESS'; const ADMIN_USERS_DEACTIVATE_FAIL = 'ADMIN_USERS_DEACTIVATE_FAIL'; @@ -309,56 +313,80 @@ const deactivateUsers = (accountIds: string[], reportId?: string) => } }; -const deleteUsers = (accountIds: string[]) => +const deleteUser = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = accountIdsToAccts(getState(), accountIds); - dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountIds }); + const nicknames = accountIdsToAccts(getState(), [accountId]); + dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountId }); return api(getState) .delete('/api/v1/pleroma/admin/users', { data: { nicknames } }) .then(({ data: nicknames }) => { - dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames, accountIds }); + dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames, accountId }); }).catch(error => { - dispatch({ type: ADMIN_USERS_DELETE_FAIL, error, accountIds }); + dispatch({ type: ADMIN_USERS_DELETE_FAIL, error, accountId }); }); }; -const approveMastodonUsers = (accountIds: string[]) => +const approveMastodonUser = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => - Promise.all(accountIds.map(accountId => { - api(getState) - .post(`/api/v1/admin/accounts/${accountId}/approve`) - .then(({ data: user }) => { - dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users: [user], accountIds: [accountId] }); - }).catch(error => { - dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountIds: [accountId] }); - }); - })); + api(getState) + .post(`/api/v1/admin/accounts/${accountId}/approve`) + .then(({ data: user }) => { + dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, user, accountId }); + }).catch(error => { + dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountId }); + }); -const approvePleromaUsers = (accountIds: string[]) => +const approvePleromaUser = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const nicknames = accountIdsToAccts(getState(), accountIds); + const nicknames = accountIdsToAccts(getState(), [accountId]); return api(getState) .patch('/api/v1/pleroma/admin/users/approve', { nicknames }) .then(({ data: { users } }) => { - dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users, accountIds }); + dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, user: users[0], accountId }); }).catch(error => { - dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountIds }); + dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountId }); }); }; -const approveUsers = (accountIds: string[]) => +const rejectMastodonUser = (accountId: string) => + (dispatch: AppDispatch, getState: () => RootState) => + api(getState) + .post(`/api/v1/admin/accounts/${accountId}/reject`) + .then(({ data: user }) => { + dispatch({ type: ADMIN_USERS_REJECT_SUCCESS, user, accountId }); + }).catch(error => { + dispatch({ type: ADMIN_USERS_REJECT_FAIL, error, accountId }); + }); + +const approveUser = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); const instance = state.instance; const features = getFeatures(instance); - dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, accountIds }); + dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, accountId }); if (features.mastodonAdmin) { - return dispatch(approveMastodonUsers(accountIds)); + return dispatch(approveMastodonUser(accountId)); } else { - return dispatch(approvePleromaUsers(accountIds)); + return dispatch(approvePleromaUser(accountId)); + } + }; + +const rejectUser = (accountId: string) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + + const instance = state.instance; + const features = getFeatures(instance); + + dispatch({ type: ADMIN_USERS_REJECT_REQUEST, accountId }); + + if (features.mastodonAdmin) { + return dispatch(rejectMastodonUser(accountId)); + } else { + return dispatch(deleteUser(accountId)); } }; @@ -562,6 +590,9 @@ export { ADMIN_USERS_APPROVE_REQUEST, ADMIN_USERS_APPROVE_SUCCESS, ADMIN_USERS_APPROVE_FAIL, + ADMIN_USERS_REJECT_REQUEST, + ADMIN_USERS_REJECT_SUCCESS, + ADMIN_USERS_REJECT_FAIL, ADMIN_USERS_DEACTIVATE_REQUEST, ADMIN_USERS_DEACTIVATE_SUCCESS, ADMIN_USERS_DEACTIVATE_FAIL, @@ -597,8 +628,9 @@ export { closeReports, fetchUsers, deactivateUsers, - deleteUsers, - approveUsers, + deleteUser, + approveUser, + rejectUser, deleteStatus, toggleStatusSensitivity, tagUsers, diff --git a/src/actions/moderation.tsx b/src/actions/moderation.tsx index 35ad6a4d7..ddee5fe1d 100644 --- a/src/actions/moderation.tsx +++ b/src/actions/moderation.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { defineMessages, IntlShape } from 'react-intl'; import { fetchAccountByUsername } from 'soapbox/actions/accounts'; -import { deactivateUsers, deleteUsers, deleteStatus, toggleStatusSensitivity } from 'soapbox/actions/admin'; +import { deactivateUsers, deleteUser, deleteStatus, toggleStatusSensitivity } from 'soapbox/actions/admin'; import { openModal } from 'soapbox/actions/modals'; import OutlineBox from 'soapbox/components/outline-box'; import { Stack, Text } from 'soapbox/components/ui'; @@ -102,7 +102,7 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () = confirm, checkbox, onConfirm: () => { - dispatch(deleteUsers([accountId])).then(() => { + dispatch(deleteUser(accountId)).then(() => { const message = intl.formatMessage(messages.userDeleted, { acct }); dispatch(fetchAccountByUsername(acct)); toast.success(message); diff --git a/src/components/account.tsx b/src/components/account.tsx index 5751b2c3a..4871079a7 100644 --- a/src/components/account.tsx +++ b/src/components/account.tsx @@ -71,6 +71,7 @@ const ProfilePopper: React.FC = ({ condition, wrapper, children }; export interface IAccount { + acct?: string; account: AccountSchema; action?: React.ReactElement; actionAlignment?: 'center' | 'top'; @@ -99,6 +100,7 @@ export interface IAccount { } const Account = ({ + acct, account, actionType, action, @@ -228,7 +230,7 @@ const Account = ({ - @{username} + @{acct ?? username} {account.pleroma?.favicon && ( diff --git a/src/features/admin/components/unapproved-account.tsx b/src/features/admin/components/unapproved-account.tsx index 503fb513e..b10d9428c 100644 --- a/src/features/admin/components/unapproved-account.tsx +++ b/src/features/admin/components/unapproved-account.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { approveUsers, deleteUsers } from 'soapbox/actions/admin'; +import { approveUser, rejectUser } from 'soapbox/actions/admin'; import { useAccount } from 'soapbox/api/hooks'; import Account from 'soapbox/components/account'; import { AuthorizeRejectButtons } from 'soapbox/components/authorize-reject-buttons'; @@ -14,18 +14,19 @@ interface IUnapprovedAccount { const UnapprovedAccount: React.FC = ({ accountId }) => { const dispatch = useAppDispatch(); - const { account } = useAccount(accountId); const adminAccount = useAppSelector(state => state.admin.users.get(accountId)); + const { account } = useAccount(adminAccount?.account || undefined); - if (!account) return null; + if (!adminAccount || !account) return null; - const handleApprove = () => dispatch(approveUsers([account.id])); - const handleReject = () => dispatch(deleteUsers([account.id])); + const handleApprove = () => dispatch(approveUser(adminAccount.id)); + const handleReject = () => dispatch(rejectUser(adminAccount.id)); return ( { - accountIds.forEach(id => { - state.update('awaitingApproval', orderedSet => orderedSet.delete(id)); - state.deleteIn(['users', id]); - }); + state.update('awaitingApproval', orderedSet => orderedSet.delete(accountId)); + state.deleteIn(['users', accountId]); }); } -function approveUsers(state: State, users: APIUser[]): State { +function approveUser(state: State, user: APIUser): State { + const normalizedUser = fixUser(user); return state.withMutations(state => { - users.forEach(user => { - const normalizedUser = fixUser(user); - state.update('awaitingApproval', orderedSet => orderedSet.delete(user.id)); - state.setIn(['users', user.id], normalizedUser); - }); + state.update('awaitingApproval', orderedSet => orderedSet.delete(user.id)); + state.setIn(['users', user.id], normalizedUser); }); } @@ -207,11 +205,13 @@ export default function admin(state: State = ReducerRecord(), action: AnyAction) return importUsers(state, action.users, action.filters, action.page); case ADMIN_USERS_DELETE_REQUEST: case ADMIN_USERS_DELETE_SUCCESS: - return deleteUsers(state, action.accountIds); + case ADMIN_USERS_REJECT_REQUEST: + case ADMIN_USERS_REJECT_SUCCESS: + return deleteUser(state, action.accountId); case ADMIN_USERS_APPROVE_REQUEST: - return state.update('awaitingApproval', set => set.subtract(action.accountIds)); + return state.update('awaitingApproval', set => set.remove(action.accountId)); case ADMIN_USERS_APPROVE_SUCCESS: - return approveUsers(state, action.users); + return approveUser(state, action.user); default: return state; }