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);