diff --git a/app/soapbox/actions/admin.js b/app/soapbox/actions/admin.js index ee957948d..0ad5932c6 100644 --- a/app/soapbox/actions/admin.js +++ b/app/soapbox/actions/admin.js @@ -1,6 +1,7 @@ import api from '../api'; import { importFetchedAccount, importFetchedStatuses } from 'soapbox/actions/importer'; import { fetchRelationships } from 'soapbox/actions/accounts'; +import { Set as ImmutableSet } from 'immutable'; export const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST'; export const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS'; diff --git a/app/soapbox/features/admin/components/latest_accounts_panel.js b/app/soapbox/features/admin/components/latest_accounts_panel.js new file mode 100644 index 000000000..37b82b6e7 --- /dev/null +++ b/app/soapbox/features/admin/components/latest_accounts_panel.js @@ -0,0 +1,54 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { injectIntl, defineMessages } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import AccountListPanel from 'soapbox/features/ui/components/account_list_panel'; +import { fetchUsers } from 'soapbox/actions/admin'; + +const messages = defineMessages({ + title: { id: 'admin.latest_accounts_panel.title', defaultMessage: 'Latest Accounts' }, +}); + +const mapStateToProps = state => ({ + accountIds: state.getIn(['admin', 'latestUsers']), +}); + +export default @connect(mapStateToProps) +@injectIntl +class LatestAccountsPanel extends ImmutablePureComponent { + + static propTypes = { + accountIds: ImmutablePropTypes.orderedSet.isRequired, + limit: PropTypes.number, + }; + + static defaultProps = { + limit: 5, + } + + componentDidMount() { + const { dispatch, limit } = this.props; + dispatch(fetchUsers(['local', 'active'], 1, null, limit)); + } + + render() { + const { intl, accountIds, limit, ...props } = this.props; + + if (!accountIds || accountIds.isEmpty()) { + return null; + } + + return ( + + ); + }; + +}; diff --git a/app/soapbox/features/ui/components/account_list_panel.js b/app/soapbox/features/ui/components/account_list_panel.js new file mode 100644 index 000000000..8563a2231 --- /dev/null +++ b/app/soapbox/features/ui/components/account_list_panel.js @@ -0,0 +1,47 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import Icon from 'soapbox/components/icon'; +import AccountContainer from '../../../containers/account_container'; + +export default class AccountListPanel extends ImmutablePureComponent { + + static propTypes = { + title: PropTypes.node.isRequired, + accountIds: ImmutablePropTypes.orderedSet.isRequired, + icon: PropTypes.string.isRequired, + limit: PropTypes.number, + }; + + static defaultProps = { + limit: Infinity, + } + + render() { + const { title, icon, accountIds, limit, ...props } = this.props; + + if (!accountIds || accountIds.isEmpty()) { + return null; + } + + return ( +
+
+ + + {title} + +
+
+
+ {accountIds.take(limit).map(accountId => ( + + ))} +
+
+
+ ); + }; + +}; diff --git a/app/soapbox/pages/admin_page.js b/app/soapbox/pages/admin_page.js index 28dd2d25f..009b599b2 100644 --- a/app/soapbox/pages/admin_page.js +++ b/app/soapbox/pages/admin_page.js @@ -2,6 +2,7 @@ import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; import LinkFooter from '../features/ui/components/link_footer'; import AdminNav from 'soapbox/features/admin/components/admin_nav'; +import LatestAccountsPanel from 'soapbox/features/admin/components/latest_accounts_panel'; export default class AdminPage extends ImmutablePureComponent { @@ -28,6 +29,7 @@ class AdminPage extends ImmutablePureComponent {
+
diff --git a/app/soapbox/reducers/__tests__/admin-test.js b/app/soapbox/reducers/__tests__/admin-test.js index 588abe7aa..e56eff6e9 100644 --- a/app/soapbox/reducers/__tests__/admin-test.js +++ b/app/soapbox/reducers/__tests__/admin-test.js @@ -11,6 +11,7 @@ describe('admin reducer', () => { reports: ImmutableMap(), openReports: ImmutableOrderedSet(), users: ImmutableMap(), + latestUsers: ImmutableOrderedSet(), awaitingApproval: ImmutableOrderedSet(), configs: ImmutableList(), needsReboot: false, diff --git a/app/soapbox/reducers/admin.js b/app/soapbox/reducers/admin.js index f7cb33fb6..b4978c218 100644 --- a/app/soapbox/reducers/admin.js +++ b/app/soapbox/reducers/admin.js @@ -12,31 +12,68 @@ import { import { Map as ImmutableMap, List as ImmutableList, + Set as ImmutableSet, OrderedSet as ImmutableOrderedSet, fromJS, + is, } from 'immutable'; -import { normalizePleromaUserFields } from 'soapbox/utils/pleroma'; const initialState = ImmutableMap({ reports: ImmutableMap(), openReports: ImmutableOrderedSet(), users: ImmutableMap(), + latestUsers: ImmutableOrderedSet(), awaitingApproval: ImmutableOrderedSet(), configs: ImmutableList(), needsReboot: false, }); -function importUsers(state, users) { +const FILTER_UNAPPROVED = ['local', 'need_approval']; +const FILTER_LATEST = ['local', 'active']; + +const filtersMatch = (f1, f2) => is(ImmutableSet(f1), ImmutableSet(f2)); +const toIds = items => items.map(item => item.id); + +const mergeSet = (state, key, users) => { + const newIds = toIds(users); + return state.update(key, ImmutableOrderedSet(), ids => ids.union(newIds)); +}; + +const replaceSet = (state, key, users) => { + const newIds = toIds(users); + return state.set(key, ImmutableOrderedSet(newIds)); +}; + +const maybeImportUnapproved = (state, users, filters) => { + if (filtersMatch(FILTER_UNAPPROVED, filters)) { + return mergeSet(state, 'awaitingApproval', users); + } else { + return state; + } +}; + +const maybeImportLatest = (state, users, filters, page) => { + if (page === 1 && filtersMatch(FILTER_LATEST, filters)) { + return replaceSet(state, 'latestUsers', users); + } else { + return state; + } +}; + +const importUser = (state, user) => ( + state.setIn(['users', user.id], ImmutableMap({ + email: user.email, + registration_reason: user.registration_reason, + })) +); + +function importUsers(state, users, filters, page) { return state.withMutations(state => { + maybeImportUnapproved(state, users, filters); + maybeImportLatest(state, users, filters, page); + users.forEach(user => { - user = normalizePleromaUserFields(user); - if (!user.is_approved) { - state.update('awaitingApproval', orderedSet => orderedSet.add(user.id)); - } - state.setIn(['users', user.id], ImmutableMap({ - email: user.email, - registration_reason: user.registration_reason, - })); + importUser(state, user); }); }); } @@ -97,7 +134,7 @@ export default function admin(state = initialState, action) { case ADMIN_REPORTS_PATCH_SUCCESS: return handleReportDiffs(state, action.reports); case ADMIN_USERS_FETCH_SUCCESS: - return importUsers(state, action.users); + return importUsers(state, action.users, action.filters, action.page); case ADMIN_USERS_DELETE_REQUEST: case ADMIN_USERS_DELETE_SUCCESS: return deleteUsers(state, action.accountIds);