Rework admin approve/reject actions

This commit is contained in:
Alex Gleason 2024-06-09 13:40:08 -05:00
parent cdc8c70078
commit 0e846784df
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
5 changed files with 83 additions and 48 deletions

View File

@ -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_SUCCESS = 'ADMIN_USERS_APPROVE_SUCCESS';
const ADMIN_USERS_APPROVE_FAIL = 'ADMIN_USERS_APPROVE_FAIL'; 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_REQUEST = 'ADMIN_USERS_DEACTIVATE_REQUEST';
const ADMIN_USERS_DEACTIVATE_SUCCESS = 'ADMIN_USERS_DEACTIVATE_SUCCESS'; const ADMIN_USERS_DEACTIVATE_SUCCESS = 'ADMIN_USERS_DEACTIVATE_SUCCESS';
const ADMIN_USERS_DEACTIVATE_FAIL = 'ADMIN_USERS_DEACTIVATE_FAIL'; 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) => { (dispatch: AppDispatch, getState: () => RootState) => {
const nicknames = accountIdsToAccts(getState(), accountIds); const nicknames = accountIdsToAccts(getState(), [accountId]);
dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountIds }); dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountId });
return api(getState) return api(getState)
.delete('/api/v1/pleroma/admin/users', { data: { nicknames } }) .delete('/api/v1/pleroma/admin/users', { data: { nicknames } })
.then(({ data: nicknames }) => { .then(({ data: nicknames }) => {
dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames, accountIds }); dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames, accountId });
}).catch(error => { }).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) => (dispatch: AppDispatch, getState: () => RootState) =>
Promise.all(accountIds.map(accountId => { api(getState)
api(getState) .post(`/api/v1/admin/accounts/${accountId}/approve`)
.post(`/api/v1/admin/accounts/${accountId}/approve`) .then(({ data: user }) => {
.then(({ data: user }) => { dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, user, accountId });
dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users: [user], accountIds: [accountId] }); }).catch(error => {
}).catch(error => { dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountId });
dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountIds: [accountId] }); });
});
}));
const approvePleromaUsers = (accountIds: string[]) => const approvePleromaUser = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => { (dispatch: AppDispatch, getState: () => RootState) => {
const nicknames = accountIdsToAccts(getState(), accountIds); const nicknames = accountIdsToAccts(getState(), [accountId]);
return api(getState) return api(getState)
.patch('/api/v1/pleroma/admin/users/approve', { nicknames }) .patch('/api/v1/pleroma/admin/users/approve', { nicknames })
.then(({ data: { users } }) => { .then(({ data: { users } }) => {
dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users, accountIds }); dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, user: users[0], accountId });
}).catch(error => { }).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) => { (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState(); const state = getState();
const instance = state.instance; const instance = state.instance;
const features = getFeatures(instance); const features = getFeatures(instance);
dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, accountIds }); dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, accountId });
if (features.mastodonAdmin) { if (features.mastodonAdmin) {
return dispatch(approveMastodonUsers(accountIds)); return dispatch(approveMastodonUser(accountId));
} else { } 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_REQUEST,
ADMIN_USERS_APPROVE_SUCCESS, ADMIN_USERS_APPROVE_SUCCESS,
ADMIN_USERS_APPROVE_FAIL, ADMIN_USERS_APPROVE_FAIL,
ADMIN_USERS_REJECT_REQUEST,
ADMIN_USERS_REJECT_SUCCESS,
ADMIN_USERS_REJECT_FAIL,
ADMIN_USERS_DEACTIVATE_REQUEST, ADMIN_USERS_DEACTIVATE_REQUEST,
ADMIN_USERS_DEACTIVATE_SUCCESS, ADMIN_USERS_DEACTIVATE_SUCCESS,
ADMIN_USERS_DEACTIVATE_FAIL, ADMIN_USERS_DEACTIVATE_FAIL,
@ -597,8 +628,9 @@ export {
closeReports, closeReports,
fetchUsers, fetchUsers,
deactivateUsers, deactivateUsers,
deleteUsers, deleteUser,
approveUsers, approveUser,
rejectUser,
deleteStatus, deleteStatus,
toggleStatusSensitivity, toggleStatusSensitivity,
tagUsers, tagUsers,

View File

@ -2,7 +2,7 @@ import React from 'react';
import { defineMessages, IntlShape } from 'react-intl'; import { defineMessages, IntlShape } from 'react-intl';
import { fetchAccountByUsername } from 'soapbox/actions/accounts'; 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 { openModal } from 'soapbox/actions/modals';
import OutlineBox from 'soapbox/components/outline-box'; import OutlineBox from 'soapbox/components/outline-box';
import { Stack, Text } from 'soapbox/components/ui'; import { Stack, Text } from 'soapbox/components/ui';
@ -102,7 +102,7 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () =
confirm, confirm,
checkbox, checkbox,
onConfirm: () => { onConfirm: () => {
dispatch(deleteUsers([accountId])).then(() => { dispatch(deleteUser(accountId)).then(() => {
const message = intl.formatMessage(messages.userDeleted, { acct }); const message = intl.formatMessage(messages.userDeleted, { acct });
dispatch(fetchAccountByUsername(acct)); dispatch(fetchAccountByUsername(acct));
toast.success(message); toast.success(message);

View File

@ -71,6 +71,7 @@ const ProfilePopper: React.FC<IProfilePopper> = ({ condition, wrapper, children
}; };
export interface IAccount { export interface IAccount {
acct?: string;
account: AccountSchema; account: AccountSchema;
action?: React.ReactElement; action?: React.ReactElement;
actionAlignment?: 'center' | 'top'; actionAlignment?: 'center' | 'top';
@ -99,6 +100,7 @@ export interface IAccount {
} }
const Account = ({ const Account = ({
acct,
account, account,
actionType, actionType,
action, action,
@ -228,7 +230,7 @@ const Account = ({
<Stack space={withAccountNote || note ? 1 : 0}> <Stack space={withAccountNote || note ? 1 : 0}>
<HStack alignItems='center' space={1}> <HStack alignItems='center' space={1}>
<Text theme='muted' size='sm' direction='ltr' truncate>@{username}</Text> <Text theme='muted' size='sm' direction='ltr' truncate>@{acct ?? username}</Text>
{account.pleroma?.favicon && ( {account.pleroma?.favicon && (
<InstanceFavicon account={account} disabled={!withLinkToProfile} /> <InstanceFavicon account={account} disabled={!withLinkToProfile} />

View File

@ -1,6 +1,6 @@
import React from 'react'; 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 { useAccount } from 'soapbox/api/hooks';
import Account from 'soapbox/components/account'; import Account from 'soapbox/components/account';
import { AuthorizeRejectButtons } from 'soapbox/components/authorize-reject-buttons'; import { AuthorizeRejectButtons } from 'soapbox/components/authorize-reject-buttons';
@ -14,18 +14,19 @@ interface IUnapprovedAccount {
const UnapprovedAccount: React.FC<IUnapprovedAccount> = ({ accountId }) => { const UnapprovedAccount: React.FC<IUnapprovedAccount> = ({ accountId }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { account } = useAccount(accountId);
const adminAccount = useAppSelector(state => state.admin.users.get(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 handleApprove = () => dispatch(approveUser(adminAccount.id));
const handleReject = () => dispatch(deleteUsers([account.id])); const handleReject = () => dispatch(rejectUser(adminAccount.id));
return ( return (
<Account <Account
key={account.id} key={adminAccount.id}
account={account} account={account}
acct={`${adminAccount.username}@${adminAccount.domain}`}
note={adminAccount?.invite_request || ''} note={adminAccount?.invite_request || ''}
action={( action={(
<AuthorizeRejectButtons <AuthorizeRejectButtons

View File

@ -19,6 +19,8 @@ import {
ADMIN_USERS_DELETE_SUCCESS, ADMIN_USERS_DELETE_SUCCESS,
ADMIN_USERS_APPROVE_REQUEST, ADMIN_USERS_APPROVE_REQUEST,
ADMIN_USERS_APPROVE_SUCCESS, ADMIN_USERS_APPROVE_SUCCESS,
ADMIN_USERS_REJECT_REQUEST,
ADMIN_USERS_REJECT_SUCCESS,
} from 'soapbox/actions/admin'; } from 'soapbox/actions/admin';
import { normalizeAdminReport, normalizeAdminAccount } from 'soapbox/normalizers'; import { normalizeAdminReport, normalizeAdminAccount } from 'soapbox/normalizers';
import { normalizeId } from 'soapbox/utils/normalizers'; import { normalizeId } from 'soapbox/utils/normalizers';
@ -120,22 +122,18 @@ function importUsers(state: State, users: APIUser[], filters: Filter[], page: nu
}); });
} }
function deleteUsers(state: State, accountIds: string[]): State { function deleteUser(state: State, accountId: string): State {
return state.withMutations(state => { return state.withMutations(state => {
accountIds.forEach(id => { state.update('awaitingApproval', orderedSet => orderedSet.delete(accountId));
state.update('awaitingApproval', orderedSet => orderedSet.delete(id)); state.deleteIn(['users', accountId]);
state.deleteIn(['users', id]);
});
}); });
} }
function approveUsers(state: State, users: APIUser[]): State { function approveUser(state: State, user: APIUser): State {
const normalizedUser = fixUser(user);
return state.withMutations(state => { return state.withMutations(state => {
users.forEach(user => { state.update('awaitingApproval', orderedSet => orderedSet.delete(user.id));
const normalizedUser = fixUser(user); 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); return importUsers(state, action.users, action.filters, action.page);
case ADMIN_USERS_DELETE_REQUEST: case ADMIN_USERS_DELETE_REQUEST:
case ADMIN_USERS_DELETE_SUCCESS: 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: 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: case ADMIN_USERS_APPROVE_SUCCESS:
return approveUsers(state, action.users); return approveUser(state, action.user);
default: default:
return state; return state;
} }