diff --git a/app/soapbox/actions/accounts.js b/app/soapbox/actions/accounts.js index bd2b199ba..5908f01f1 100644 --- a/app/soapbox/actions/accounts.js +++ b/app/soapbox/actions/accounts.js @@ -130,7 +130,9 @@ export function fetchAccount(id) { return (dispatch, getState) => { dispatch(fetchRelationships([id])); - if (getState().getIn(['accounts', id], null) !== null) { + const account = getState().getIn(['accounts', id]); + + if (account && !account.get('should_refetch')) { return; } @@ -156,7 +158,15 @@ export function fetchAccount(id) { export function fetchAccountByUsername(username) { return (dispatch, getState) => { + const account = getState().get('accounts').find(account => account.get('acct') === username); + + if (account) { + dispatch(fetchAccount(account.get('id'))); + return; + } + api(getState).get(`/api/v1/accounts/${username}`).then(response => { + dispatch(fetchRelationships([response.data.id])); dispatch(importFetchedAccount(response.data)); }).then(() => { dispatch(fetchAccountSuccess()); diff --git a/app/soapbox/actions/admin.js b/app/soapbox/actions/admin.js index 6a93b1682..43119708d 100644 --- a/app/soapbox/actions/admin.js +++ b/app/soapbox/actions/admin.js @@ -1,5 +1,6 @@ import api from '../api'; import { importFetchedAccount, importFetchedStatuses } from 'soapbox/actions/importer'; +import { fetchRelationships } from 'soapbox/actions/accounts'; export const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST'; export const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS'; @@ -124,54 +125,61 @@ export function closeReports(ids) { return patchReports(ids, 'closed'); } -export function fetchUsers(params) { +export function fetchUsers(filters = [], page = 1, pageSize = 50) { return (dispatch, getState) => { - dispatch({ type: ADMIN_USERS_FETCH_REQUEST, params }); + const params = { filters: filters.join(), page, page_size: pageSize }; + + dispatch({ type: ADMIN_USERS_FETCH_REQUEST, filters, page, pageSize }); return api(getState) .get('/api/pleroma/admin/users', { params }) - .then(({ data }) => { - dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, data, params }); + .then(({ data: { users, count, page_size: pageSize } }) => { + dispatch(fetchRelationships(users.map(user => user.id))); + dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, users, count, pageSize, filters, page }); + return { users, count, pageSize }; }).catch(error => { - dispatch({ type: ADMIN_USERS_FETCH_FAIL, error, params }); + dispatch({ type: ADMIN_USERS_FETCH_FAIL, error, filters, page, pageSize }); }); }; } -export function deactivateUsers(nicknames) { +export function deactivateUsers(accountIds) { return (dispatch, getState) => { - dispatch({ type: ADMIN_USERS_DEACTIVATE_REQUEST, nicknames }); + const nicknames = nicknamesFromIds(getState, accountIds); + dispatch({ type: ADMIN_USERS_DEACTIVATE_REQUEST, accountIds }); return api(getState) .patch('/api/pleroma/admin/users/deactivate', { nicknames }) .then(({ data: { users } }) => { - dispatch({ type: ADMIN_USERS_DEACTIVATE_SUCCESS, users, nicknames }); + dispatch({ type: ADMIN_USERS_DEACTIVATE_SUCCESS, users, accountIds }); }).catch(error => { - dispatch({ type: ADMIN_USERS_DEACTIVATE_FAIL, error, nicknames }); + dispatch({ type: ADMIN_USERS_DEACTIVATE_FAIL, error, accountIds }); }); }; } -export function deleteUsers(nicknames) { +export function deleteUsers(accountIds) { return (dispatch, getState) => { - dispatch({ type: ADMIN_USERS_DELETE_REQUEST, nicknames }); + const nicknames = nicknamesFromIds(getState, accountIds); + dispatch({ type: ADMIN_USERS_DELETE_REQUEST, accountIds }); return api(getState) .delete('/api/pleroma/admin/users', { data: { nicknames } }) .then(({ data: nicknames }) => { - dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames }); + dispatch({ type: ADMIN_USERS_DELETE_SUCCESS, nicknames, accountIds }); }).catch(error => { - dispatch({ type: ADMIN_USERS_DELETE_FAIL, error, nicknames }); + dispatch({ type: ADMIN_USERS_DELETE_FAIL, error, accountIds }); }); }; } -export function approveUsers(nicknames) { +export function approveUsers(accountIds) { return (dispatch, getState) => { - dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, nicknames }); + const nicknames = nicknamesFromIds(getState, accountIds); + dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, accountIds }); return api(getState) .patch('/api/pleroma/admin/users/approve', { nicknames }) .then(({ data: { users } }) => { - dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users, nicknames }); + dispatch({ type: ADMIN_USERS_APPROVE_SUCCESS, users, accountIds }); }).catch(error => { - dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, nicknames }); + dispatch({ type: ADMIN_USERS_APPROVE_FAIL, error, accountIds }); }); }; } diff --git a/app/soapbox/actions/moderation.js b/app/soapbox/actions/moderation.js index cce860de8..cb84803bd 100644 --- a/app/soapbox/actions/moderation.js +++ b/app/soapbox/actions/moderation.js @@ -36,7 +36,7 @@ export function deactivateUserModal(intl, accountId, afterConfirm = () => {}) { message: intl.formatMessage(messages.deactivateUserPrompt, { acct }), confirm: intl.formatMessage(messages.deactivateUserConfirm, { name }), onConfirm: () => { - dispatch(deactivateUsers([acct])).then(() => { + dispatch(deactivateUsers([accountId])).then(() => { const message = intl.formatMessage(messages.userDeactivated, { acct }); dispatch(snackbar.success(message)); afterConfirm(); @@ -74,7 +74,7 @@ export function deleteUserModal(intl, accountId, afterConfirm = () => {}) { confirm, checkbox, onConfirm: () => { - dispatch(deleteUsers([acct])).then(() => { + dispatch(deleteUsers([accountId])).then(() => { const message = intl.formatMessage(messages.userDeleted, { acct }); dispatch(fetchAccountByUsername(acct)); dispatch(snackbar.success(message)); diff --git a/app/soapbox/features/admin/awaiting_approval.js b/app/soapbox/features/admin/awaiting_approval.js index 7706eb4a8..0d95fa685 100644 --- a/app/soapbox/features/admin/awaiting_approval.js +++ b/app/soapbox/features/admin/awaiting_approval.js @@ -5,24 +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 IconButton from 'soapbox/components/icon_button'; import ScrollableList from 'soapbox/components/scrollable_list'; -import { fetchUsers, deleteUsers, approveUsers } from 'soapbox/actions/admin'; -import snackbar from 'soapbox/actions/snackbar'; +import UnapprovedAccount from './components/unapproved_account'; +import { fetchUsers } from 'soapbox/actions/admin'; const messages = defineMessages({ heading: { id: 'column.admin.awaiting_approval', defaultMessage: 'Awaiting Approval' }, emptyMessage: { id: 'admin.awaiting_approval.empty_message', defaultMessage: 'There is nobody waiting for approval. When a new user signs up, you can review them here.' }, - approved: { id: 'admin.awaiting_approval.approved_message', defaultMessage: '{acct} was approved!' }, - rejected: { id: 'admin.awaiting_approval.rejected_message', defaultMessage: '{acct} was rejected.' }, }); -const mapStateToProps = state => { - const nicknames = state.getIn(['admin', 'awaitingApproval']); - return { - users: nicknames.toList().map(nickname => state.getIn(['admin', 'users', nickname])), - }; -}; +const mapStateToProps = state => ({ + accountIds: state.getIn(['admin', 'awaitingApproval']), +}); export default @connect(mapStateToProps) @injectIntl @@ -30,7 +24,7 @@ class AwaitingApproval extends ImmutablePureComponent { static propTypes = { intl: PropTypes.object.isRequired, - users: ImmutablePropTypes.list.isRequired, + accountIds: ImmutablePropTypes.orderedSet.isRequired, }; state = { @@ -39,51 +33,26 @@ class AwaitingApproval extends ImmutablePureComponent { componentDidMount() { const { dispatch } = this.props; - const params = { page: 1, filters: 'local,need_approval' }; - dispatch(fetchUsers(params)) + dispatch(fetchUsers(['local', 'need_approval'])) .then(() => this.setState({ isLoading: false })) .catch(() => {}); } - handleApprove = nickname => { - const { dispatch, intl } = this.props; - return e => { - dispatch(approveUsers([nickname])).then(() => { - const message = intl.formatMessage(messages.approved, { acct: `@${nickname}` }); - dispatch(snackbar.success(message)); - }).catch(() => {}); - }; - } - - handleReject = nickname => { - const { dispatch, intl } = this.props; - return e => { - dispatch(deleteUsers([nickname])).then(() => { - const message = intl.formatMessage(messages.rejected, { acct: `@${nickname}` }); - dispatch(snackbar.info(message)); - }).catch(() => {}); - }; - } - render() { - const { intl, users } = this.props; + const { intl, accountIds } = this.props; const { isLoading } = this.state; - const showLoading = isLoading && users.count() === 0; + const showLoading = isLoading && accountIds.count() === 0; return ( - - {users.map((user, i) => ( -
-
-
@{user.get('nickname')}
-
{user.get('registration_reason')}
-
-
- - -
-
+ + {accountIds.map(id => ( + ))}
diff --git a/app/soapbox/features/admin/components/unapproved_account.js b/app/soapbox/features/admin/components/unapproved_account.js new file mode 100644 index 000000000..578a55c0c --- /dev/null +++ b/app/soapbox/features/admin/components/unapproved_account.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { defineMessages, injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import IconButton from 'soapbox/components/icon_button'; +import { deleteUsers, approveUsers } from 'soapbox/actions/admin'; +import { makeGetAccount } from 'soapbox/selectors'; +import snackbar from 'soapbox/actions/snackbar'; + +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.' }, +}); + +const makeMapStateToProps = () => { + const getAccount = makeGetAccount(); + + const mapStateToProps = (state, { accountId }) => { + return { + account: getAccount(state, accountId), + }; + }; + + return mapStateToProps; +}; + +export default @connect(makeMapStateToProps) +@injectIntl +class UnapprovedAccount extends ImmutablePureComponent { + + static propTypes = { + intl: PropTypes.object.isRequired, + account: ImmutablePropTypes.map.isRequired, + }; + + handleApprove = () => { + const { dispatch, intl, account } = this.props; + + dispatch(approveUsers([account.get('id')])) + .then(() => { + const message = intl.formatMessage(messages.approved, { acct: `@${account.get('acct')}` }); + dispatch(snackbar.success(message)); + }) + .catch(() => {}); + } + + handleReject = () => { + const { dispatch, intl, account } = this.props; + + dispatch(deleteUsers([account.get('id')])) + .then(() => { + const message = intl.formatMessage(messages.rejected, { acct: `@${account.get('acct')}` }); + dispatch(snackbar.info(message)); + }) + .catch(() => {}); + } + + render() { + const { account } = this.props; + + return ( +
+
+
@{account.get('acct')}
+
{account.getIn(['pleroma', 'admin', 'registration_reason'])}
+
+
+ + +
+
+ ); + } + +} diff --git a/app/soapbox/features/admin/index.js b/app/soapbox/features/admin/index.js index 0ed82bc66..0c2d90781 100644 --- a/app/soapbox/features/admin/index.js +++ b/app/soapbox/features/admin/index.js @@ -1,5 +1,6 @@ import React from 'react'; import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl'; +import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; @@ -92,14 +93,14 @@ class Dashboard extends ImmutablePureComponent { }
- +
-
+
{retention &&
@@ -112,14 +113,14 @@ class Dashboard extends ImmutablePureComponent {
}
- +
-
+
diff --git a/app/soapbox/features/admin/user_index.js b/app/soapbox/features/admin/user_index.js new file mode 100644 index 000000000..71a173822 --- /dev/null +++ b/app/soapbox/features/admin/user_index.js @@ -0,0 +1,95 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +import { debounce } from 'lodash'; +import { fetchUsers } from 'soapbox/actions/admin'; +import { FormattedMessage } from 'react-intl'; +import AccountContainer from 'soapbox/containers/account_container'; +import Column from 'soapbox/features/ui/components/column'; +import ScrollableList from 'soapbox/components/scrollable_list'; +import { Set as ImmutableSet, OrderedSet as ImmutableOrderedSet, is } from 'immutable'; + +export default @connect() +class UserIndex extends ImmutablePureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + }; + + state = { + isLoading: true, + filters: ImmutableSet(['local', 'active']), + accountIds: ImmutableOrderedSet(), + total: Infinity, + pageSize: 50, + page: 0, + } + + clearState = () => { + this.setState({ + isLoading: true, + page: 0, + }); + } + + fetchNextPage = () => { + const { filters, page, pageSize } = this.state; + const nextPage = page + 1; + + this.props.dispatch(fetchUsers(filters, nextPage, pageSize)) + .then(({ users, count }) => { + const newIds = users.map(user => user.id); + + this.setState({ + isLoading: false, + accountIds: this.state.accountIds.union(newIds), + total: count, + page: nextPage, + }); + }) + .catch(() => {}); + } + + componentDidMount() { + this.fetchNextPage(); + } + + componentDidUpdate(prevProps, prevState) { + const { filters, q } = this.state; + + if (!is(filters, prevState.filters) || !is(q, prevState.q)) { + this.clearState(); + this.fetchNextPage(); + } + } + + handleLoadMore = debounce(() => { + this.fetchNextPage(); + }, 2000, { leading: true }); + + render() { + const { accountIds, isLoading } = this.state; + const hasMore = accountIds.count() < this.state.total; + + const showLoading = isLoading && accountIds.isEmpty(); + + return ( + + } + > + {accountIds.map(id => + , + )} + + + ); + } + +} diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index a140cd0a7..fd522c07f 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -95,6 +95,7 @@ import { ModerationLog, CryptoDonate, ScheduledStatuses, + UserIndex, } from './util/async-components'; // Dummy import, to make sure that ends up in the application bundle. @@ -265,6 +266,7 @@ class SwitchingColumnsArea extends React.PureComponent { + @@ -428,7 +430,7 @@ class UI extends React.PureComponent { if (isStaff(account)) { this.props.dispatch(fetchReports({ state: 'open' })); - this.props.dispatch(fetchUsers({ page: 1, filters: 'local,need_approval' })); + this.props.dispatch(fetchUsers(['local', 'need_approval'])); } if (isAdmin(account)) { diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 9d1fcae1d..c1bb39301 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -241,3 +241,7 @@ export function CryptoDonate() { export function ScheduledStatuses() { return import(/* webpackChunkName: "features/scheduled_statuses" */'../../scheduled_statuses'); } + +export function UserIndex() { + return import(/* webpackChunkName: "features/admin/user_index" */'../../admin/user_index'); +} diff --git a/app/soapbox/reducers/accounts.js b/app/soapbox/reducers/accounts.js index 7bab5aad4..761356507 100644 --- a/app/soapbox/reducers/accounts.js +++ b/app/soapbox/reducers/accounts.js @@ -13,6 +13,7 @@ import { } from 'immutable'; import { normalizePleromaUserFields } from 'soapbox/utils/pleroma'; import { + ADMIN_USERS_FETCH_SUCCESS, ADMIN_USERS_TAG_REQUEST, ADMIN_USERS_TAG_FAIL, ADMIN_USERS_UNTAG_REQUEST, @@ -22,7 +23,12 @@ import { ADMIN_REMOVE_PERMISSION_GROUP_REQUEST, ADMIN_REMOVE_PERMISSION_GROUP_FAIL, } from 'soapbox/actions/admin'; -import { ADMIN_USERS_DELETE_REQUEST } from 'soapbox/actions/admin'; +import { + ADMIN_USERS_DELETE_REQUEST, + ADMIN_USERS_DELETE_FAIL, + ADMIN_USERS_DEACTIVATE_REQUEST, + ADMIN_USERS_DEACTIVATE_FAIL, +} from 'soapbox/actions/admin'; const initialState = ImmutableMap(); @@ -79,17 +85,10 @@ const removeTags = (state, accountIds, tags) => { }); }; -const nicknamesToIds = (state, nicknames) => { - return nicknames.map(nickname => { - return state.find(account => account.get('acct') === nickname, null, ImmutableMap()).get('id'); - }); -}; - -const setDeactivated = (state, nicknames) => { - const ids = nicknamesToIds(state, nicknames); +const setActive = (state, accountIds, active) => { return state.withMutations(state => { - ids.forEach(id => { - state.setIn([id, 'pleroma', 'is_active'], false); + accountIds.forEach(id => { + state.setIn([id, 'pleroma', 'is_active'], active); }); }); }; @@ -121,6 +120,67 @@ const removePermission = (state, accountIds, permissionGroup) => { }); }; +const buildAccount = adminUser => fromJS({ + id: adminUser.get('id'), + username: adminUser.get('nickname').split('@')[0], + acct: adminUser.get('nickname'), + display_name: adminUser.get('display_name'), + display_name_html: adminUser.get('display_name'), + note: '', + url: adminUser.get('url'), + avatar: adminUser.get('avatar'), + avatar_static: adminUser.get('avatar'), + header: '', + header_static: '', + emojis: [], + fields: [], + pleroma: { + is_active: adminUser.get('is_active'), + is_confirmed: adminUser.get('is_confirmed'), + is_admin: adminUser.getIn(['roles', 'admin']), + is_moderator: adminUser.getIn(['roles', 'moderator']), + tags: adminUser.get('tags'), + }, + source: { + pleroma: { + actor_type: adminUser.get('actor_type'), + }, + }, + should_refetch: true, +}); + +const mergeAdminUser = (account, adminUser) => { + return account.withMutations(account => { + account.set('display_name', adminUser.get('display_name')); + account.set('avatar', adminUser.get('avatar')); + account.set('avatar_static', adminUser.get('avatar')); + account.setIn(['pleroma', 'is_active'], adminUser.get('is_active')); + account.setIn(['pleroma', 'is_admin'], adminUser.getIn(['roles', 'admin'])); + account.setIn(['pleroma', 'is_moderator'], adminUser.getIn(['roles', 'moderator'])); + account.setIn(['pleroma', 'is_confirmed'], adminUser.get('is_confirmed')); + account.setIn(['pleroma', 'tags'], adminUser.get('tags')); + }); +}; + +const importAdminUser = (state, adminUser) => { + const id = adminUser.get('id'); + const account = state.get(id); + + if (!account) { + return state.set(id, buildAccount(adminUser)); + } else { + return state.set(id, mergeAdminUser(account, adminUser)); + } +}; + +const importAdminUsers = (state, adminUsers) => { + return state.withMutations(state => { + fromJS(adminUsers).forEach(adminUser => { + importAdminUser(state, adminUser); + }); + }); +}; + export default function accounts(state = initialState, action) { switch(action.type) { case ACCOUNT_IMPORT: @@ -149,7 +209,13 @@ export default function accounts(state = initialState, action) { case ADMIN_ADD_PERMISSION_GROUP_FAIL: return removePermission(state, action.accountIds, action.permissionGroup); case ADMIN_USERS_DELETE_REQUEST: - return setDeactivated(state, action.nicknames); + case ADMIN_USERS_DEACTIVATE_REQUEST: + return setActive(state, action.accountIds, false); + case ADMIN_USERS_DELETE_FAIL: + case ADMIN_USERS_DEACTIVATE_FAIL: + return setActive(state, action.accountIds, true); + case ADMIN_USERS_FETCH_SUCCESS: + return importAdminUsers(state, action.users); default: return state; } diff --git a/app/soapbox/reducers/admin.js b/app/soapbox/reducers/admin.js index 960df70f6..f7cb33fb6 100644 --- a/app/soapbox/reducers/admin.js +++ b/app/soapbox/reducers/admin.js @@ -31,18 +31,21 @@ function importUsers(state, users) { users.forEach(user => { user = normalizePleromaUserFields(user); if (!user.is_approved) { - state.update('awaitingApproval', orderedSet => orderedSet.add(user.nickname)); + state.update('awaitingApproval', orderedSet => orderedSet.add(user.id)); } - state.setIn(['users', user.nickname], fromJS(user)); + state.setIn(['users', user.id], ImmutableMap({ + email: user.email, + registration_reason: user.registration_reason, + })); }); }); } -function deleteUsers(state, nicknames) { +function deleteUsers(state, accountIds) { return state.withMutations(state => { - nicknames.forEach(nickname => { - state.update('awaitingApproval', orderedSet => orderedSet.delete(nickname)); - state.deleteIn(['users', nickname]); + accountIds.forEach(id => { + state.update('awaitingApproval', orderedSet => orderedSet.delete(id)); + state.deleteIn(['users', id]); }); }); } @@ -94,12 +97,12 @@ 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.data.users); + return importUsers(state, action.users); case ADMIN_USERS_DELETE_REQUEST: case ADMIN_USERS_DELETE_SUCCESS: - return deleteUsers(state, action.nicknames); + return deleteUsers(state, action.accountIds); case ADMIN_USERS_APPROVE_REQUEST: - return state.update('awaitingApproval', set => set.subtract(action.nicknames)); + return state.update('awaitingApproval', set => set.subtract(action.accountIds)); case ADMIN_USERS_APPROVE_SUCCESS: return approveUsers(state, action.users); default: diff --git a/app/soapbox/selectors/index.js b/app/soapbox/selectors/index.js index 0615db229..9b222921e 100644 --- a/app/soapbox/selectors/index.js +++ b/app/soapbox/selectors/index.js @@ -5,6 +5,7 @@ const getAccountBase = (state, id) => state.getIn(['accounts', id], null const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null); const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null); const getAccountMoved = (state, id) => state.getIn(['accounts', state.getIn(['accounts', id, 'moved'])]); +const getAccountAdminData = (state, id) => state.getIn(['admin', 'users', id]); const getAccountPatron = (state, id) => { const url = state.getIn(['accounts', id, 'url']); return state.getIn(['patron', 'accounts', url]); @@ -16,8 +17,9 @@ export const makeGetAccount = () => { getAccountCounters, getAccountRelationship, getAccountMoved, + getAccountAdminData, getAccountPatron, - ], (base, counters, relationship, moved, patron) => { + ], (base, counters, relationship, moved, admin, patron) => { if (base === null) { return null; } @@ -26,6 +28,7 @@ export const makeGetAccount = () => { map.set('relationship', relationship); map.set('moved', moved); map.set('patron', patron); + map.setIn(['pleroma', 'admin'], admin); }); }); };