diff --git a/src/actions/admin.ts b/src/actions/admin.ts index 45b5076fb..e6c3f5225 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'; @@ -266,6 +270,14 @@ const fetchUsers = (filters: string[] = [], page = 1, query?: string | null, pag } }; +const revokeName = (accountId: string, reportId?: string) => + (dispatch: AppDispatch, getState: () => RootState) => + api(getState) + .post(`/api/v1/admin/accounts/${accountId}/action`, { + type: 'revoke_name', + report_id: reportId, + }); + const deactivateMastodonUsers = (accountIds: string[], reportId?: string) => (dispatch: AppDispatch, getState: () => RootState) => Promise.all(accountIds.map(accountId => { @@ -309,56 +321,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 +598,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 +636,10 @@ export { closeReports, fetchUsers, deactivateUsers, - deleteUsers, - approveUsers, + deleteUser, + approveUser, + rejectUser, + revokeName, 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 ( = () => { const instance = useInstance(); const dispatch = useAppDispatch(); const { account } = useOwnAccount(); - const { relay, signer } = useNostr(); + const { mutate } = useRequestName(); + + const { data: approvedNames } = useNames(); + const { data: pendingNames } = usePendingNames(); - const admin = instance.nostr?.pubkey; - const pubkey = account?.nostr?.pubkey; const [username, setUsername] = useState(''); - - const { events: labels } = useNostrReq( - (admin && pubkey) - ? [{ kinds: [1985], authors: [admin], '#L': ['nip05'], '#p': [pubkey] }] - : [], - ); + const [reason, setReason] = useState(''); if (!account) return null; - const updateNip05 = async (nip05: string): Promise => { - if (account.source?.nostr?.nip05 === nip05) return; + const updateName = async (name: string): Promise => { + if (account.source?.nostr?.nip05 === name) return; try { - await dispatch(patchMe({ nip05 })); + await dispatch(patchMe({ nip05: name })); toast.success(intl.formatMessage(messages.success)); } catch (e) { toast.error(intl.formatMessage(messages.error)); } }; - const submit = async () => { - if (!admin || !signer || !relay) return; - - const event = await signer.signEvent({ - kind: 5950, - content: '', - tags: [ - ['i', `${username}@${instance.domain}`, 'text'], - ['p', admin], - ], - created_at: Math.floor(Date.now() / 1000), - }); - - await relay.event(event); + const submit = () => { + const name = `${username}@${instance.domain}`; + mutate({ name, reason }); }; return ( - - {labels.map((label) => { - const identifier = label.tags.find(([name]) => name === 'l')?.[1]; - if (!identifier) return null; +
+
+ setUsername(e.target.value)} /> +