diff --git a/app/controllers/api/v1/groups/removed_accounts_controller.rb b/app/controllers/api/v1/groups/removed_accounts_controller.rb index e0b5b428..d6cba234 100644 --- a/app/controllers/api/v1/groups/removed_accounts_controller.rb +++ b/app/controllers/api/v1/groups/removed_accounts_controller.rb @@ -56,7 +56,7 @@ class Api::V1::Groups::RemovedAccountsController < Api::BaseController return if unlimited? if records_continue? - api_v1_group_accounts_url pagination_params(max_id: pagination_max_id) + api_v1_group_removed_accounts_url pagination_params(max_id: pagination_max_id) end end @@ -64,7 +64,7 @@ class Api::V1::Groups::RemovedAccountsController < Api::BaseController return if unlimited? unless @accounts.empty? - api_v1_group_accounts_url pagination_params(since_id: pagination_since_id) + api_v1_group_removed_accounts_url pagination_params(since_id: pagination_since_id) end end diff --git a/app/javascript/gabsocial/actions/groups.js b/app/javascript/gabsocial/actions/groups.js index b67a3116..110bf25a 100644 --- a/app/javascript/gabsocial/actions/groups.js +++ b/app/javascript/gabsocial/actions/groups.js @@ -31,6 +31,14 @@ export const GROUP_MEMBERS_EXPAND_REQUEST = 'GROUP_MEMBERS_EXPAND_REQUEST'; export const GROUP_MEMBERS_EXPAND_SUCCESS = 'GROUP_MEMBERS_EXPAND_SUCCESS'; export const GROUP_MEMBERS_EXPAND_FAIL = 'GROUP_MEMBERS_EXPAND_FAIL'; +export const GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST = 'GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST'; +export const GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS'; +export const GROUP_REMOVED_ACCOUNTS_FETCH_FAIL = 'GROUP_REMOVED_ACCOUNTS_FETCH_FAIL'; + +export const GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST = 'GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST'; +export const GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS'; +export const GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL = 'GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL'; + export const fetchGroup = id => (dispatch, getState) => { if (!me) return; @@ -294,4 +302,94 @@ export function expandMembersFail(id, error) { id, error, }; +}; + +export function fetchRemovedAccounts(id) { + return (dispatch, getState) => { + if (!me) return; + + dispatch(fetchRemovedAccountsRequest(id)); + + api(getState).get(`/api/v1/groups/${id}/removed_accounts`).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(importFetchedAccounts(response.data)); + dispatch(fetchRemovedAccountsSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => { + dispatch(fetchRemovedAccountsFail(id, error)); + }); + }; +}; + +export function fetchRemovedAccountsRequest(id) { + return { + type: GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST, + id, + }; +}; + +export function fetchRemovedAccountsSuccess(id, accounts, next) { + return { + type: GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS, + id, + accounts, + next, + }; +}; + +export function fetchRemovedAccountsFail(id, error) { + return { + type: GROUP_REMOVED_ACCOUNTS_FETCH_FAIL, + id, + error, + }; +}; + +export function expandRemovedAccounts(id) { + return (dispatch, getState) => { + if (!me) return; + + const url = getState().getIn(['user_lists', 'groups_removed_accounts', id, 'next']); + + if (url === null) { + return; + } + + dispatch(expandRemovedAccountsRequest(id)); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + + dispatch(importFetchedAccounts(response.data)); + dispatch(expandRemovedAccountsSuccess(id, response.data, next ? next.uri : null)); + dispatch(fetchRelationships(response.data.map(item => item.id))); + }).catch(error => { + dispatch(expandRemovedAccountsFail(id, error)); + }); + }; +}; + +export function expandRemovedAccountsRequest(id) { + return { + type: GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST, + id, + }; +}; + +export function expandRemovedAccountsSuccess(id, accounts, next) { + return { + type: GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS, + id, + accounts, + next, + }; +}; + +export function expandRemovedAccountsFail(id, error) { + return { + type: GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL, + id, + error, + }; }; \ No newline at end of file diff --git a/app/javascript/gabsocial/features/groups/removed_accounts/index.js b/app/javascript/gabsocial/features/groups/removed_accounts/index.js new file mode 100644 index 00000000..b91c77fa --- /dev/null +++ b/app/javascript/gabsocial/features/groups/removed_accounts/index.js @@ -0,0 +1,73 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { debounce } from 'lodash'; +import LoadingIndicator from '../../../components/loading_indicator'; +import { + fetchRemovedAccounts, + expandRemovedAccounts, +} from '../../../actions/groups'; +import { FormattedMessage } from 'react-intl'; +import AccountContainer from '../../../containers/account_container'; +import Column from '../../ui/components/column'; +import ScrollableList from '../../../components/scrollable_list'; + +const mapStateToProps = (state, { params: { id } }) => ({ + group: state.getIn(['groups', id]), + accountIds: state.getIn(['user_lists', 'groups_removed_accounts', id, 'items']), + hasMore: !!state.getIn(['user_lists', 'groups_removed_accounts', id, 'next']), +}); + +export default @connect(mapStateToProps) +class GroupRemovedAccounts extends ImmutablePureComponent { + + static propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list, + hasMore: PropTypes.bool, + }; + + componentWillMount () { + const { params: { id } } = this.props; + + this.props.dispatch(fetchRemovedAccounts(id)); + } + + componentWillReceiveProps (nextProps) { + if (nextProps.params.id !== this.props.params.id) { + this.props.dispatch(fetchRemovedAccounts(nextProps.params.id)); + } + } + + handleLoadMore = debounce(() => { + this.props.dispatch(expandRemovedAccounts(this.props.params.id)); + }, 300, { leading: true }); + + render () { + const { accountIds, hasMore, group } = this.props; + + if (!group || !accountIds) { + return ( + + + + ); + } + + return ( + + } + > + {accountIds.map(id => )} + + + ); + } +} diff --git a/app/javascript/gabsocial/features/groups/timeline/components/header.js b/app/javascript/gabsocial/features/groups/timeline/components/header.js index 9944cb87..58d2cf2f 100644 --- a/app/javascript/gabsocial/features/groups/timeline/components/header.js +++ b/app/javascript/gabsocial/features/groups/timeline/components/header.js @@ -5,10 +5,12 @@ import Button from 'gabsocial/components/button'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl } from 'react-intl'; import { NavLink } from 'react-router-dom'; +import DropdownMenuContainer from '../../../../containers/dropdown_menu_container'; const messages = defineMessages({ join: { id: 'groups.join', defaultMessage: 'Join group' }, leave: { id: 'groups.leave', defaultMessage: 'Leave group' }, + removed_accounts: { id: 'groups.removed_accounts', defaultMessage: 'Removed Accounts' } }); export default @injectIntl @@ -36,6 +38,16 @@ class Header extends ImmutablePureComponent { } } + getAdminMenu() { + const { group, intl } = this.props; + + const menu = [ + { text: intl.formatMessage(messages.removed_accounts), to: `/groups/${group.get('id')}/removed_accounts` }, + ]; + + return ; + } + render () { const { group, relationships } = this.props; @@ -54,6 +66,7 @@ class Header extends ImmutablePureComponent { Posts Members {this.getActionButton()} + {relationships.get('admin') && this.getAdminMenu()} diff --git a/app/javascript/gabsocial/features/ui/index.js b/app/javascript/gabsocial/features/ui/index.js index 952ba43e..afb0d5d0 100644 --- a/app/javascript/gabsocial/features/ui/index.js +++ b/app/javascript/gabsocial/features/ui/index.js @@ -56,6 +56,7 @@ import { Groups, GroupTimeline, GroupMembers, + GroupRemovedAccounts, } from './util/async-components'; import { me, meUsername } from '../../initial_state'; import { previewState as previewMediaState } from './components/media_modal'; @@ -176,6 +177,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/javascript/gabsocial/features/ui/util/async-components.js b/app/javascript/gabsocial/features/ui/util/async-components.js index 9eb368c0..fbc0cc07 100644 --- a/app/javascript/gabsocial/features/ui/util/async-components.js +++ b/app/javascript/gabsocial/features/ui/util/async-components.js @@ -38,6 +38,10 @@ export function GroupMembers () { return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/members'); } +export function GroupRemovedAccounts () { + return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/removed_accounts'); +} + export function Groups () { return import(/* webpackChunkName: "features/groups/index" */'../../groups/index'); } diff --git a/app/javascript/gabsocial/reducers/user_lists.js b/app/javascript/gabsocial/reducers/user_lists.js index 97b7f5e9..609dc473 100644 --- a/app/javascript/gabsocial/reducers/user_lists.js +++ b/app/javascript/gabsocial/reducers/user_lists.js @@ -24,6 +24,8 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { GROUP_MEMBERS_FETCH_SUCCESS, GROUP_MEMBERS_EXPAND_SUCCESS, + GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS, + GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS, } from '../actions/groups'; const initialState = ImmutableMap({ @@ -35,6 +37,7 @@ const initialState = ImmutableMap({ blocks: ImmutableMap(), mutes: ImmutableMap(), groups: ImmutableMap(), + groups_removed_accounts: ImmutableMap(), }); const normalizeList = (state, type, id, accounts, next) => { @@ -83,6 +86,10 @@ export default function userLists(state = initialState, action) { return normalizeList(state, 'groups', action.id, action.accounts, action.next); case GROUP_MEMBERS_EXPAND_SUCCESS: return appendToList(state, 'groups', action.id, action.accounts, action.next); + case GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS: + return normalizeList(state, 'groups_removed_accounts', action.id, action.accounts, action.next); + case GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS: + return appendToList(state, 'groups_removed_accounts', action.id, action.accounts, action.next); default: return state; } diff --git a/app/javascript/styles/gabsocial/components/group-detail.scss b/app/javascript/styles/gabsocial/components/group-detail.scss index ea5ca94d..05e115a4 100644 --- a/app/javascript/styles/gabsocial/components/group-detail.scss +++ b/app/javascript/styles/gabsocial/components/group-detail.scss @@ -44,6 +44,11 @@ float: right; margin: 7px; } + + div { + float: right; + margin: 4px; + } } }