diff --git a/app/soapbox/actions/moderation.tsx b/app/soapbox/actions/moderation.tsx index 5b0a4a5f2..c236a2986 100644 --- a/app/soapbox/actions/moderation.tsx +++ b/app/soapbox/actions/moderation.tsx @@ -112,27 +112,6 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () = })); }; -const rejectUserModal = (intl: IntlShape, accountId: string, afterConfirm = () => {}) => - (dispatch: AppDispatch, getState: () => RootState) => { - const state = getState(); - const acct = state.accounts.get(accountId)!.acct; - const name = state.accounts.get(accountId)!.username; - - dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/user-off.svg'), - heading: intl.formatMessage(messages.rejectUserHeading, { acct }), - message: intl.formatMessage(messages.rejectUserPrompt, { acct }), - confirm: intl.formatMessage(messages.rejectUserConfirm, { name }), - onConfirm: () => { - dispatch(deleteUsers([accountId])) - .then(() => { - afterConfirm(); - }) - .catch(() => {}); - }, - })); - }; - const toggleStatusSensitivityModal = (intl: IntlShape, statusId: string, sensitive: boolean, afterConfirm = () => {}) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); @@ -178,7 +157,6 @@ const deleteStatusModal = (intl: IntlShape, statusId: string, afterConfirm = () export { deactivateUserModal, deleteUserModal, - rejectUserModal, toggleStatusSensitivityModal, deleteStatusModal, }; diff --git a/app/soapbox/components/authorize-reject-buttons.tsx b/app/soapbox/components/authorize-reject-buttons.tsx index 66d8d1d4a..9edd44189 100644 --- a/app/soapbox/components/authorize-reject-buttons.tsx +++ b/app/soapbox/components/authorize-reject-buttons.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react'; +import clsx from 'clsx'; +import React, { useEffect, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { HStack, IconButton, Text } from 'soapbox/components/ui'; @@ -6,67 +7,133 @@ import { HStack, IconButton, Text } from 'soapbox/components/ui'; interface IAuthorizeRejectButtons { onAuthorize(): Promise | unknown onReject(): Promise | unknown + countdown?: number } /** Buttons to approve or reject a pending item, usually an account. */ -const AuthorizeRejectButtons: React.FC = ({ onAuthorize, onReject }) => { - const [state, setState] = useState<'authorized' | 'rejected' | 'pending'>('pending'); +const AuthorizeRejectButtons: React.FC = ({ onAuthorize, onReject, countdown }) => { + const [state, setState] = useState<'authorizing' | 'rejecting' | 'authorized' | 'rejected' | 'pending'>('pending'); + const timeout = useRef(); - async function handleAuthorize() { - try { - await onAuthorize(); - setState('authorized'); - } catch (e) { - console.error(e); + function handleAction( + present: 'authorizing' | 'rejecting', + past: 'authorized' | 'rejected', + action: () => Promise | unknown, + ): void { + if (state === present) { + if (timeout.current) { + clearTimeout(timeout.current); + } + setState('pending'); + } else { + const doAction = async () => { + try { + await action(); + setState(past); + } catch (e) { + console.error(e); + } + }; + if (typeof countdown === 'number') { + setState(present); + timeout.current = setTimeout(doAction, countdown); + } else { + doAction(); + } } } - async function handleReject() { - try { - await onReject(); - setState('rejected'); - } catch (e) { - console.error(e); - } - } + const handleAuthorize = async () => handleAction('authorizing', 'authorized', onAuthorize); + const handleReject = async () => handleAction('rejecting', 'rejected', onReject); + + useEffect(() => { + return () => { + if (timeout.current) { + clearTimeout(timeout.current); + } + }; + }, []); switch (state) { - case 'pending': - return ( - - - - - ); case 'authorized': return ( -
- - - -
+ } /> ); case 'rejected': return ( -
- - - -
+ } /> + ); + default: + return ( + + + + ); } }; +interface IActionEmblem { + text: React.ReactNode +} + +const ActionEmblem: React.FC = ({ text }) => { + return ( +
+ + {text} + +
+ ); +}; + +interface IAuthorizeRejectButton { + theme: 'primary' | 'danger' + icon: string + action(): void + isLoading?: boolean + disabled?: boolean +} + +const AuthorizeRejectButton: React.FC = ({ theme, icon, action, isLoading, disabled }) => { + return ( +
+ + {(isLoading) && ( +
+ )} +
+ ); +}; + export { AuthorizeRejectButtons }; \ No newline at end of file diff --git a/app/soapbox/features/admin/components/unapproved-account.tsx b/app/soapbox/features/admin/components/unapproved-account.tsx index 26f4b661e..cf99baa6e 100644 --- a/app/soapbox/features/admin/components/unapproved-account.tsx +++ b/app/soapbox/features/admin/components/unapproved-account.tsx @@ -1,18 +1,10 @@ import React, { useCallback } from 'react'; -import { defineMessages, useIntl } from 'react-intl'; -import { approveUsers } from 'soapbox/actions/admin'; -import { rejectUserModal } from 'soapbox/actions/moderation'; +import { approveUsers, deleteUsers } from 'soapbox/actions/admin'; import { AuthorizeRejectButtons } from 'soapbox/components/authorize-reject-buttons'; import { Stack, HStack, Text } from 'soapbox/components/ui'; import { useAppSelector, useAppDispatch } from 'soapbox/hooks'; import { makeGetAccount } from 'soapbox/selectors'; -import toast from 'soapbox/toast'; - -const messages = defineMessages({ - approved: { id: 'admin.awaiting_approval.approved_message', defaultMessage: '{acct} was approved!' }, - rejected: { id: 'admin.awaiting_approval.rejected_message', defaultMessage: '{acct} was rejected.' }, -}); interface IUnapprovedAccount { accountId: string @@ -20,7 +12,6 @@ interface IUnapprovedAccount { /** Displays an unapproved account for moderation purposes. */ const UnapprovedAccount: React.FC = ({ accountId }) => { - const intl = useIntl(); const dispatch = useAppDispatch(); const getAccount = useCallback(makeGetAccount(), []); @@ -29,23 +20,8 @@ const UnapprovedAccount: React.FC = ({ accountId }) => { if (!account) return null; - const handleApprove = () => { - return dispatch(approveUsers([account.id])) - .then(() => { - const message = intl.formatMessage(messages.approved, { acct: `@${account.acct}` }); - toast.success(message); - }); - }; - - const handleReject = () => { - return new Promise((resolve) => { - dispatch(rejectUserModal(intl, account.id, () => { - const message = intl.formatMessage(messages.rejected, { acct: `@${account.acct}` }); - toast.info(message); - resolve(); - })); - }); - }; + const handleApprove = () => dispatch(approveUsers([account.id])); + const handleReject = () => dispatch(deleteUsers([account.id])); return ( @@ -62,6 +38,7 @@ const UnapprovedAccount: React.FC = ({ accountId }) => { diff --git a/app/soapbox/features/follow-requests/components/account-authorize.tsx b/app/soapbox/features/follow-requests/components/account-authorize.tsx index 91f492523..9e1387ba2 100644 --- a/app/soapbox/features/follow-requests/components/account-authorize.tsx +++ b/app/soapbox/features/follow-requests/components/account-authorize.tsx @@ -29,6 +29,7 @@ const AccountAuthorize: React.FC = ({ id }) => { } /> diff --git a/app/soapbox/features/group/group-membership-requests.tsx b/app/soapbox/features/group/group-membership-requests.tsx index d98517af8..3b5f91d5d 100644 --- a/app/soapbox/features/group/group-membership-requests.tsx +++ b/app/soapbox/features/group/group-membership-requests.tsx @@ -42,6 +42,7 @@ const MembershipRequest: React.FC = ({ account, onAuthorize, ); diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 1d4d12a31..d50a4e619 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -91,9 +91,7 @@ "admin.announcements.edit": "Edit", "admin.announcements.ends_at": "Ends at:", "admin.announcements.starts_at": "Starts at:", - "admin.awaiting_approval.approved_message": "{acct} was approved!", "admin.awaiting_approval.empty_message": "There is nobody waiting for approval. When a new user signs up, you can review them here.", - "admin.awaiting_approval.rejected_message": "{acct} was rejected.", "admin.dashboard.registration_mode.approval_hint": "Users can sign up, but their account only gets activated when an admin approves it.", "admin.dashboard.registration_mode.approval_label": "Approval Required", "admin.dashboard.registration_mode.closed_hint": "Nobody can sign up. You can still invite people.",