diff --git a/app/soapbox/actions/admin.js b/app/soapbox/actions/admin.js index 118f26992..4329aa670 100644 --- a/app/soapbox/actions/admin.js +++ b/app/soapbox/actions/admin.js @@ -12,6 +12,14 @@ export const ADMIN_USERS_FETCH_REQUEST = 'ADMIN_USERS_FETCH_REQUEST'; export const ADMIN_USERS_FETCH_SUCCESS = 'ADMIN_USERS_FETCH_SUCCESS'; export const ADMIN_USERS_FETCH_FAIL = 'ADMIN_USERS_FETCH_FAIL'; +export const ADMIN_USERS_DELETE_REQUEST = 'ADMIN_USERS_DELETE_REQUEST'; +export const ADMIN_USERS_DELETE_SUCCESS = 'ADMIN_USERS_DELETE_SUCCESS'; +export const ADMIN_USERS_DELETE_FAIL = 'ADMIN_USERS_DELETE_FAIL'; + +export const ADMIN_USERS_APPROVE_REQUEST = 'ADMIN_USERS_APPROVE_REQUEST'; +export const ADMIN_USERS_APPROVE_SUCCESS = 'ADMIN_USERS_APPROVE_SUCCESS'; +export const ADMIN_USERS_APPROVE_FAIL = 'ADMIN_USERS_APPROVE_FAIL'; + export function updateAdminConfig(params) { return (dispatch, getState) => { dispatch({ type: ADMIN_CONFIG_UPDATE_REQUEST }); @@ -50,3 +58,29 @@ export function fetchUsers(params) { }); }; } + +export function deleteUsers(nicknames) { + return (dispatch, getState) => { + dispatch({ type: ADMIN_USERS_DELETE_REQUEST, nicknames }); + return api(getState) + .delete('/api/pleroma/admin/users', { data: { nicknames } }) + .then(({ data: nicknames }) => { + dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames }); + }).catch(error => { + dispatch({ type: ADMIN_USERS_DELETE_FAIL, error, nicknames }); + }); + }; +} + +export function approveUsers(nicknames) { + return (dispatch, getState) => { + dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, nicknames }); + return api(getState) + .patch('/api/pleroma/admin/users/approve', { nicknames }) + .then(({ data: { users } }) => { + dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users, nicknames }); + }).catch(error => { + dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, nicknames }); + }); + }; +} diff --git a/app/soapbox/features/admin/awaiting_approval.js b/app/soapbox/features/admin/awaiting_approval.js index 8e716d644..503e66809 100644 --- a/app/soapbox/features/admin/awaiting_approval.js +++ b/app/soapbox/features/admin/awaiting_approval.js @@ -5,16 +5,18 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Column from '../ui/components/column'; -import { fetchUsers } from 'soapbox/actions/admin'; +import IconButton from 'soapbox/components/icon_button'; +import ScrollableList from 'soapbox/components/scrollable_list'; +import { fetchUsers, deleteUsers, approveUsers } from 'soapbox/actions/admin'; const messages = defineMessages({ heading: { id: 'column.admin.awaiting_approval', defaultMessage: 'Awaiting Approval' }, }); const mapStateToProps = state => { - const userIds = state.getIn(['admin', 'awaitingApproval']); + const nicknames = state.getIn(['admin', 'awaitingApproval']); return { - users: userIds.map(id => state.getIn(['admin', 'users', id])), + users: nicknames.toList().map(nickname => state.getIn(['admin', 'users', nickname])), }; }; @@ -27,18 +29,52 @@ class AwaitingApproval extends ImmutablePureComponent { users: ImmutablePropTypes.list.isRequired, }; + state = { + isLoading: true, + } + componentDidMount() { - this.props.dispatch(fetchUsers({ page: 1, filters: 'local,need_approval' })); + const { dispatch } = this.props; + const params = { page: 1, filters: 'local,need_approval' }; + dispatch(fetchUsers(params)) + .then(() => this.setState({ isLoading: false })) + .catch(() => {}); + } + + handleApprove = nickname => { + const { dispatch } = this.props; + return e => { + dispatch(approveUsers([nickname])); + }; + } + + handleReject = nickname => { + const { dispatch } = this.props; + return e => { + dispatch(deleteUsers([nickname])); + }; } render() { const { intl, users } = this.props; + const { isLoading } = this.state; return ( - {users.map((user, i) => ( -
{user.get('nickname')}
- ))} + + {users.map((user, i) => ( +
+
+
@{user.get('nickname')}
+
{user.get('registration_reason')}
+
+
+ + +
+
+ ))} +
); } diff --git a/app/soapbox/reducers/admin.js b/app/soapbox/reducers/admin.js index f120d8794..45a21cc67 100644 --- a/app/soapbox/reducers/admin.js +++ b/app/soapbox/reducers/admin.js @@ -1,6 +1,8 @@ import { ADMIN_REPORTS_FETCH_SUCCESS, ADMIN_USERS_FETCH_SUCCESS, + ADMIN_USERS_DELETE_SUCCESS, + ADMIN_USERS_APPROVE_SUCCESS, } from '../actions/admin'; import { Map as ImmutableMap, @@ -20,9 +22,27 @@ function importUsers(state, users) { return state.withMutations(state => { users.forEach(user => { if (user.approval_pending) { - state.update('awaitingApproval', orderedSet => orderedSet.add(user.id)); + state.update('awaitingApproval', orderedSet => orderedSet.add(user.nickname)); } - state.setIn(['users', user.id], fromJS(user)); + state.setIn(['users', user.nickname], fromJS(user)); + }); + }); +} + +function deleteUsers(state, nicknames) { + return state.withMutations(state => { + nicknames.forEach(nickname => { + state.update('awaitingApproval', orderedSet => orderedSet.delete(nickname)); + state.deleteIn(['users', nickname]); + }); + }); +} + +function approveUsers(state, users) { + return state.withMutations(state => { + users.forEach(user => { + state.update('awaitingApproval', orderedSet => orderedSet.delete(user.nickname)); + state.setIn(['users', user.nickname], fromJS(user)); }); }); } @@ -39,6 +59,10 @@ export default function admin(state = initialState, action) { } case ADMIN_USERS_FETCH_SUCCESS: return importUsers(state, action.data.users); + case ADMIN_USERS_DELETE_SUCCESS: + return deleteUsers(state, action.nicknames); + case ADMIN_USERS_APPROVE_SUCCESS: + return approveUsers(state, action.users); default: return state; } diff --git a/app/styles/components/admin.scss b/app/styles/components/admin.scss index c986d52e4..2712016e2 100644 --- a/app/styles/components/admin.scss +++ b/app/styles/components/admin.scss @@ -67,3 +67,30 @@ border-bottom: 1px solid var(--accent-color--med); } } + +.unapproved-account { + padding: 15px 20px; + font-size: 14px; + display: flex; + + &__nickname { + font-weight: bold; + } + + &__reason { + padding: 5px 0 5px 15px; + border-left: 3px solid hsla(var(--primary-text-color_hsl), 0.4); + color: var(--primary-text-color--faint); + } + + &__actions { + margin-left: auto; + padding-left: 20px; + display: flex; + flex-wrap: nowrap; + } +} + +.slist .item-list article:nth-child(2n-1) .unapproved-account { + background-color: hsla(var(--accent-color_hsl), 0.07); +}