Added join requests to Groups
• Added: - join requests to Groups - redux actions, reducers - controller and functionality for fetching join requests, accepting, rejecting join requests
This commit is contained in:
		
							parent
							
								
									a8056f80a2
								
							
						
					
					
						commit
						217aab9faa
					
				
							
								
								
									
										97
									
								
								app/controllers/api/v1/groups/requests_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								app/controllers/api/v1/groups/requests_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,97 @@ | |||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class Api::V1::Groups::RequestsController < Api::BaseController | ||||||
|  |   include Authorization | ||||||
|  | 
 | ||||||
|  |   before_action -> { doorkeeper_authorize! :read, :'read:groups' }, only: [:show] | ||||||
|  |   before_action -> { doorkeeper_authorize! :write, :'write:groups' }, except: [:show] | ||||||
|  | 
 | ||||||
|  |   before_action :require_user! | ||||||
|  |   before_action :set_group | ||||||
|  | 
 | ||||||
|  |   after_action :insert_pagination_headers, only: :show | ||||||
|  | 
 | ||||||
|  |   def show | ||||||
|  |     @accounts = load_accounts | ||||||
|  |     render json: @accounts, each_serializer: REST::AccountSerializer | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def create | ||||||
|  |     authorize @group, :leave? | ||||||
|  |     GroupJoinRequest.where(group: @group, account_id: current_account.id).destroy_all | ||||||
|  |     render json: @group, serializer: REST::GroupRelationshipSerializer, relationships: relationships | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def approve_request | ||||||
|  |     GroupJoinRequest.where(group: @group, account_id: params[:accountId]).destroy_all | ||||||
|  |     GroupAccount.create(group: @group, account_id: params[:accountId]) | ||||||
|  |     render json: {"message": "ok"} | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def reject_request | ||||||
|  |     GroupJoinRequest.where(group: @group, account_id: params[:accountId]).destroy_all | ||||||
|  |     render json: {"message": "ok"} | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   private | ||||||
|  | 
 | ||||||
|  |   def relationships | ||||||
|  |     GroupRelationshipsPresenter.new([@group.id], current_user.account_id) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def set_group | ||||||
|  |     @group = Group.find(params[:group_id]) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def load_accounts | ||||||
|  |     if unlimited? | ||||||
|  |       @group.join_requests.includes(:account_stat).all | ||||||
|  |     else | ||||||
|  |       @group.join_requests.includes(:account_stat).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def insert_pagination_headers | ||||||
|  |     set_pagination_headers(next_path, prev_path) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def next_path | ||||||
|  |     return if unlimited? | ||||||
|  | 
 | ||||||
|  |     if records_continue? | ||||||
|  |       api_v1_group_join_requests_url pagination_params(max_id: pagination_max_id) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def prev_path | ||||||
|  |     return if unlimited? | ||||||
|  | 
 | ||||||
|  |     unless @accounts.empty? | ||||||
|  |       api_v1_group_join_requests_url pagination_params(since_id: pagination_since_id) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def pagination_max_id | ||||||
|  |     @accounts.last.id | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def pagination_since_id | ||||||
|  |     @accounts.first.id | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def records_continue? | ||||||
|  |     @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def pagination_params(core_params) | ||||||
|  |     params.slice(:limit).permit(:limit).merge(core_params) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def unlimited? | ||||||
|  |     params[:limit] == '0' | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def group_account_params | ||||||
|  |     params.permit(:role, :write_permissions) | ||||||
|  |   end | ||||||
|  | end | ||||||
| @ -55,6 +55,20 @@ export const GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_CRE | |||||||
| export const GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS'; | export const GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS'; | ||||||
| export const GROUP_REMOVED_ACCOUNTS_CREATE_FAIL    = 'GROUP_REMOVED_ACCOUNTS_CREATE_FAIL'; | export const GROUP_REMOVED_ACCOUNTS_CREATE_FAIL    = 'GROUP_REMOVED_ACCOUNTS_CREATE_FAIL'; | ||||||
| 
 | 
 | ||||||
|  | export const GROUP_JOIN_REQUESTS_FETCH_REQUEST = 'GROUP_JOIN_REQUESTS_FETCH_REQUEST' | ||||||
|  | export const GROUP_JOIN_REQUESTS_FETCH_SUCCESS = 'GROUP_JOIN_REQUESTS_FETCH_SUCCESS' | ||||||
|  | export const GROUP_JOIN_REQUESTS_FETCH_FAIL = 'GROUP_JOIN_REQUESTS_FETCH_FAIL' | ||||||
|  | 
 | ||||||
|  | export const GROUP_JOIN_REQUESTS_EXPAND_REQUEST = 'GROUP_JOIN_REQUESTS_EXPAND_REQUEST' | ||||||
|  | export const GROUP_JOIN_REQUESTS_EXPAND_SUCCESS = 'GROUP_JOIN_REQUESTS_EXPAND_SUCCESS' | ||||||
|  | export const GROUP_JOIN_REQUESTS_EXPAND_FAIL = 'GROUP_JOIN_REQUESTS_EXPAND_FAIL' | ||||||
|  | 
 | ||||||
|  | export const GROUP_JOIN_REQUESTS_APPROVE_SUCCESS = 'GROUP_JOIN_REQUESTS_APPROVE_SUCCESS' | ||||||
|  | export const GROUP_JOIN_REQUESTS_APPROVE_FAIL = 'GROUP_JOIN_REQUESTS_APPROVE_FAIL' | ||||||
|  | 
 | ||||||
|  | export const GROUP_JOIN_REQUESTS_REJECT_SUCCESS = 'GROUP_JOIN_REQUESTS_REJECT_SUCCESS' | ||||||
|  | export const GROUP_JOIN_REQUESTS_REJECT_FAIL = 'GROUP_JOIN_REQUESTS_REJECT_FAIL' | ||||||
|  | 
 | ||||||
| export const GROUP_REMOVE_STATUS_REQUEST = 'GROUP_REMOVE_STATUS_REQUEST'; | export const GROUP_REMOVE_STATUS_REQUEST = 'GROUP_REMOVE_STATUS_REQUEST'; | ||||||
| export const GROUP_REMOVE_STATUS_SUCCESS = 'GROUP_REMOVE_STATUS_SUCCESS'; | export const GROUP_REMOVE_STATUS_SUCCESS = 'GROUP_REMOVE_STATUS_SUCCESS'; | ||||||
| export const GROUP_REMOVE_STATUS_FAIL    = 'GROUP_REMOVE_STATUS_FAIL'; | export const GROUP_REMOVE_STATUS_FAIL    = 'GROUP_REMOVE_STATUS_FAIL'; | ||||||
| @ -596,6 +610,149 @@ export function updateRoleFail(groupId, id, error) { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | export function fetchJoinRequests(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     if (!me) return | ||||||
|  | 
 | ||||||
|  |     dispatch(fetchJoinRequestsRequest(id)) | ||||||
|  | 
 | ||||||
|  |     api(getState).get(`/api/v1/groups/${id}/join_requests`).then((response) => { | ||||||
|  |       const next = getLinks(response).refs.find(link => link.rel === 'next') | ||||||
|  | 
 | ||||||
|  |       dispatch(importFetchedAccounts(response.data)) | ||||||
|  |       dispatch(fetchJoinRequestsSuccess(id, response.data, next ? next.uri : null)) | ||||||
|  |       dispatch(fetchRelationships(response.data.map(item => item.id))) | ||||||
|  |     }).catch((error) => { | ||||||
|  |       dispatch(fetchJoinRequestsFail(id, error)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function fetchJoinRequestsRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_FETCH_REQUEST, | ||||||
|  |     id, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function fetchJoinRequestsSuccess(id, accounts, next) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_FETCH_SUCCESS, | ||||||
|  |     id, | ||||||
|  |     accounts, | ||||||
|  |     next, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function fetchJoinRequestsFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_FETCH_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function expandJoinRequests(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     if (!me) return | ||||||
|  | 
 | ||||||
|  |     const url = getState().getIn(['user_lists', 'group_join_requests', id, 'next']) | ||||||
|  |     const isLoading = getState().getIn(['user_lists', 'group_join_requests', id, 'isLoading']) | ||||||
|  | 
 | ||||||
|  |     if (url === null || isLoading) return | ||||||
|  | 
 | ||||||
|  |     dispatch(expandJoinRequestsRequest(id)) | ||||||
|  | 
 | ||||||
|  |     api(getState).get(url).then(response => { | ||||||
|  |       const next = getLinks(response).refs.find(link => link.rel === 'next') | ||||||
|  | 
 | ||||||
|  |       dispatch(importFetchedAccounts(response.data)) | ||||||
|  |       dispatch(expandJoinRequestsSuccess(id, response.data, next ? next.uri : null)) | ||||||
|  |       dispatch(fetchRelationships(response.data.map(item => item.id))) | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(expandJoinRequestsFail(id, error)) | ||||||
|  |     }) | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function expandJoinRequestsRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_EXPAND_REQUEST, | ||||||
|  |     id, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function expandJoinRequestsSuccess(id, accounts, next) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_EXPAND_SUCCESS, | ||||||
|  |     id, | ||||||
|  |     accounts, | ||||||
|  |     next, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function expandJoinRequestsFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_EXPAND_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const approveJoinRequest = (accountId, groupId) => (dispatch, getState) => { | ||||||
|  |   if (!me) return | ||||||
|  | 
 | ||||||
|  |   api(getState).post(`/api/v1/groups/${groupId}/join_requests/approve`, { accountId }).then((response) => { | ||||||
|  |     dispatch(approveJoinRequestSuccess(accountId, groupId)) | ||||||
|  |   }).catch((error) => { | ||||||
|  |     dispatch(approveJoinRequestFail(accountId, groupId, error)) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function approveJoinRequestSuccess(accountId, groupId) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_APPROVE_SUCCESS, | ||||||
|  |     accountId, | ||||||
|  |     groupId, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function approveJoinRequestFail(accountId, groupId, error) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_APPROVE_FAIL, | ||||||
|  |     accountId, | ||||||
|  |     groupId, | ||||||
|  |     error, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const rejectJoinRequest = (accountId, groupId) => (dispatch, getState) => { | ||||||
|  |   if (!me) return | ||||||
|  | 
 | ||||||
|  |   api(getState).delete(`/api/v1/groups/${groupId}/join_requests/reject`, { accountId }).then((response) => { | ||||||
|  |     dispatch(rejectJoinRequestSuccess(accountId, groupId)) | ||||||
|  |   }).catch((error) => { | ||||||
|  |     dispatch(rejectJoinRequestFail(accountId, groupId, error)) | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function rejectJoinRequestSuccess(accountId, groupId) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_REJECT_SUCCESS, | ||||||
|  |     accountId, | ||||||
|  |     groupId, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function rejectJoinRequestFail(accountId, groupId, error) { | ||||||
|  |   return { | ||||||
|  |     type: GROUP_JOIN_REQUESTS_REJECT_FAIL, | ||||||
|  |     accountId, | ||||||
|  |     groupId, | ||||||
|  |     error, | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function pinGroupStatus(groupId, statusId) { | export function pinGroupStatus(groupId, statusId) { | ||||||
|   return (dispatch, getState) => { |   return (dispatch, getState) => { | ||||||
|     if (!me) return |     if (!me) return | ||||||
|  | |||||||
							
								
								
									
										130
									
								
								app/javascript/gabsocial/features/group_join_requests.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								app/javascript/gabsocial/features/group_join_requests.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,130 @@ | |||||||
|  | import React from 'react' | ||||||
|  | import PropTypes from 'prop-types' | ||||||
|  | import { connect } from 'react-redux' | ||||||
|  | import ImmutablePureComponent from 'react-immutable-pure-component' | ||||||
|  | import ImmutablePropTypes from 'react-immutable-proptypes' | ||||||
|  | import debounce from 'lodash.debounce' | ||||||
|  | import isObject from 'lodash.isobject' | ||||||
|  | import { FormattedMessage } from 'react-intl' | ||||||
|  | import { me } from '../initial_state' | ||||||
|  | import { | ||||||
|  | 	fetchJoinRequests, | ||||||
|  | 	expandJoinRequests, | ||||||
|  | 	rejectJoinRequest, | ||||||
|  | 	approveJoinRequest, | ||||||
|  | } from '../actions/groups' | ||||||
|  | import Account from '../components/account' | ||||||
|  | import ColumnIndicator from '../components/column_indicator' | ||||||
|  | import Block from '../components/block' | ||||||
|  | import BlockHeading from '../components/block_heading' | ||||||
|  | import ScrollableList from '../components/scrollable_list' | ||||||
|  | 
 | ||||||
|  | class GroupJoinRequests extends ImmutablePureComponent { | ||||||
|  | 
 | ||||||
|  | 	componentWillMount() { | ||||||
|  | 		const { groupId } = this.props | ||||||
|  | 
 | ||||||
|  | 		this.props.onFetchJoinRequests(groupId) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	componentWillReceiveProps(nextProps) { | ||||||
|  | 		if (nextProps.groupId !== this.props.groupId) { | ||||||
|  | 			this.props.onFetchJoinRequests(nextProps.groupId) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	handleLoadMore = debounce(() => { | ||||||
|  | 		this.props.onExpandJoinRequests(this.props.groupId) | ||||||
|  | 	}, 300, { leading: true }) | ||||||
|  | 
 | ||||||
|  | 	render() { | ||||||
|  | 		const { | ||||||
|  | 			accountIds, | ||||||
|  | 			hasMore, | ||||||
|  | 			group, | ||||||
|  | 			groupId, | ||||||
|  | 			relationships, | ||||||
|  | 		} = this.props | ||||||
|  | 
 | ||||||
|  | 		if (!group || !relationships) return <ColumnIndicator type='loading' /> | ||||||
|  | 
 | ||||||
|  | 		const isAdminOrMod = relationships ? (relationships.get('admin') || relationships.get('moderator')) : false | ||||||
|  | 
 | ||||||
|  | 		if (!isAdminOrMod) return <ColumnIndicator type='missing' /> | ||||||
|  | 			 | ||||||
|  | 		return ( | ||||||
|  | 			<Block> | ||||||
|  | 				<BlockHeading title='Group Member Requests' /> | ||||||
|  | 				<div className={_s.d}> | ||||||
|  | 					<ScrollableList | ||||||
|  | 						scrollKey='group-members' | ||||||
|  | 						hasMore={hasMore} | ||||||
|  | 						showLoading={(!group || !accountIds || !relationships)} | ||||||
|  | 						onLoadMore={this.handleLoadMore} | ||||||
|  | 						emptyMessage={<FormattedMessage id='group.requests.empty' defaultMessage='This group does not have any member requests.' />} | ||||||
|  | 					> | ||||||
|  | 						{ | ||||||
|  | 							accountIds && accountIds.map((id) => ( | ||||||
|  | 								<Account | ||||||
|  | 									compact | ||||||
|  | 									key={id} | ||||||
|  | 									id={id} | ||||||
|  | 									showDismiss | ||||||
|  | 									dismissAction={() => { | ||||||
|  | 										this.props.onRejectJoinRequest(id, groupId) | ||||||
|  | 									}} | ||||||
|  | 									actionIcon={(!isAdminOrMod || id === me) ? undefined : 'check'} | ||||||
|  | 									onActionClick={(data, event) => { | ||||||
|  | 										this.props.onApproveJoinRequest(id, groupId) | ||||||
|  | 									}} | ||||||
|  | 								/> | ||||||
|  | 							)) | ||||||
|  | 						} | ||||||
|  | 					</ScrollableList> | ||||||
|  | 				</div> | ||||||
|  | 			</Block> | ||||||
|  | 		) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const mapStateToProps = (state, { params }) => { | ||||||
|  | 	const groupId = isObject(params) ? params['id'] : -1 | ||||||
|  | 	const group = groupId === -1 ? null : state.getIn(['groups', groupId]) | ||||||
|  | 
 | ||||||
|  | 	return { | ||||||
|  | 		group, | ||||||
|  | 		groupId, | ||||||
|  | 		relationships: state.getIn(['group_relationships', groupId]), | ||||||
|  | 		accountIds: state.getIn(['user_lists', 'group_join_requests', groupId, 'items']), | ||||||
|  | 		hasMore: !!state.getIn(['user_lists', 'group_join_requests', groupId, 'next']), | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const mapDispatchToProps = (dispatch) => ({ | ||||||
|  | 	onFetchJoinRequests(groupId) { | ||||||
|  | 		dispatch(fetchJoinRequests(groupId)) | ||||||
|  | 	}, | ||||||
|  | 	onExpandJoinRequests(groupId) { | ||||||
|  | 		dispatch(expandJoinRequests(groupId)) | ||||||
|  | 	}, | ||||||
|  | 	onRejectJoinRequest(accountId, groupId) { | ||||||
|  | 		dispatch(rejectJoinRequest(accountId, groupId)) | ||||||
|  | 	}, | ||||||
|  | 	onApproveJoinRequest(accountId, groupId) { | ||||||
|  | 		dispatch(approveJoinRequest(accountId, groupId)) | ||||||
|  | 	}, | ||||||
|  | }) | ||||||
|  | 
 | ||||||
|  | GroupJoinRequests.propTypes = { | ||||||
|  | 	group: ImmutablePropTypes.map, | ||||||
|  | 	groupId: PropTypes.string.isRequired, | ||||||
|  | 	accountIds: ImmutablePropTypes.list, | ||||||
|  | 	hasMore: PropTypes.bool, | ||||||
|  | 	onExpandJoinRequests: PropTypes.func.isRequired, | ||||||
|  | 	onFetchJoinRequests: PropTypes.func.isRequired, | ||||||
|  | 	onRejectJoinRequest: PropTypes.func.isRequired, | ||||||
|  | 	onApproveJoinRequest: PropTypes.func.isRequired, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default connect(mapStateToProps, mapDispatchToProps)(GroupJoinRequests) | ||||||
| @ -64,6 +64,7 @@ import { | |||||||
|   GroupCollectionTimeline, |   GroupCollectionTimeline, | ||||||
|   GroupCreate, |   GroupCreate, | ||||||
|   GroupAbout, |   GroupAbout, | ||||||
|  |   GroupJoinRequests, | ||||||
|   GroupMembers, |   GroupMembers, | ||||||
|   GroupRemovedAccounts, |   GroupRemovedAccounts, | ||||||
|   GroupTimeline, |   GroupTimeline, | ||||||
| @ -193,6 +194,7 @@ class SwitchingArea extends React.PureComponent { | |||||||
| 
 | 
 | ||||||
|         <WrappedRoute path='/groups/create' page={ModalPage} component={GroupCreate} content={children} componentParams={{ title: 'Create Group', page: 'create-group' }} /> |         <WrappedRoute path='/groups/create' page={ModalPage} component={GroupCreate} content={children} componentParams={{ title: 'Create Group', page: 'create-group' }} /> | ||||||
|         <WrappedRoute path='/groups/:id/members' page={GroupPage} component={GroupMembers} content={children} /> |         <WrappedRoute path='/groups/:id/members' page={GroupPage} component={GroupMembers} content={children} /> | ||||||
|  |         <WrappedRoute path='/groups/:id/requests' page={GroupPage} component={GroupJoinRequests} content={children} /> | ||||||
|         <WrappedRoute path='/groups/:id/removed-accounts' page={GroupPage} component={GroupRemovedAccounts} content={children} /> |         <WrappedRoute path='/groups/:id/removed-accounts' page={GroupPage} component={GroupRemovedAccounts} content={children} /> | ||||||
|         <WrappedRoute path='/groups/:id/edit' page={ModalPage} component={GroupCreate} content={children} componentParams={{ title: 'Edit Group', page: 'edit-group' }} /> |         <WrappedRoute path='/groups/:id/edit' page={ModalPage} component={GroupCreate} content={children} componentParams={{ title: 'Edit Group', page: 'edit-group' }} /> | ||||||
|         <WrappedRoute path='/groups/:id/about' publicRoute page={GroupPage} component={GroupAbout} content={children} /> |         <WrappedRoute path='/groups/:id/about' publicRoute page={GroupPage} component={GroupAbout} content={children} /> | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ export function GroupCreate() { return import(/* webpackChunkName: "features/gro | |||||||
| export function GroupCreateModal() { return import(/* webpackChunkName: "components/group_create_modal" */'../../../components/modal/group_create_modal') } | export function GroupCreateModal() { return import(/* webpackChunkName: "components/group_create_modal" */'../../../components/modal/group_create_modal') } | ||||||
| export function GroupDeleteModal() { return import(/* webpackChunkName: "components/group_delete_modal" */'../../../components/modal/group_delete_modal') } | export function GroupDeleteModal() { return import(/* webpackChunkName: "components/group_delete_modal" */'../../../components/modal/group_delete_modal') } | ||||||
| export function GroupInfoPanel() { return import(/* webpackChunkName: "components/group_info_panel" */'../../../components/panel/group_info_panel') } | export function GroupInfoPanel() { return import(/* webpackChunkName: "components/group_info_panel" */'../../../components/panel/group_info_panel') } | ||||||
| // export function GroupJoinRequests() { return import(/* webpackChunkName: "features/group_join_requests" */'../../group_join_requests') }
 | export function GroupJoinRequests() { return import(/* webpackChunkName: "features/group_join_requests" */'../../group_join_requests') } | ||||||
| export function GroupListSortOptionsPopover() { return import(/* webpackChunkName: "components/group_list_sort_options_popover" */'../../../components/popover/group_list_sort_options_popover') } | export function GroupListSortOptionsPopover() { return import(/* webpackChunkName: "components/group_list_sort_options_popover" */'../../../components/popover/group_list_sort_options_popover') } | ||||||
| export function GroupMemberOptionsPopover() { return import(/* webpackChunkName: "components/group_member_options_popover" */'../../../components/popover/group_member_options_popover') } | export function GroupMemberOptionsPopover() { return import(/* webpackChunkName: "components/group_member_options_popover" */'../../../components/popover/group_member_options_popover') } | ||||||
| export function GroupMembers() { return import(/* webpackChunkName: "features/group_members" */'../../group_members') } | export function GroupMembers() { return import(/* webpackChunkName: "features/group_members" */'../../group_members') } | ||||||
|  | |||||||
| @ -8,6 +8,8 @@ import { fetchGroup } from '../actions/groups' | |||||||
| import PageTitle from '../features/ui/util/page_title' | import PageTitle from '../features/ui/util/page_title' | ||||||
| import GroupLayout from '../layouts/group_layout' | import GroupLayout from '../layouts/group_layout' | ||||||
| import TimelineComposeBlock from '../components/timeline_compose_block' | import TimelineComposeBlock from '../components/timeline_compose_block' | ||||||
|  | import Block from '../components/block' | ||||||
|  | import ColumnIndicator from '../components/column_indicator' | ||||||
| import Divider from '../components/divider' | import Divider from '../components/divider' | ||||||
| 
 | 
 | ||||||
| class GroupPage extends ImmutablePureComponent { | class GroupPage extends ImmutablePureComponent { | ||||||
| @ -28,6 +30,11 @@ class GroupPage extends ImmutablePureComponent { | |||||||
| 		const groupTitle = !!group ? group.get('title') : '' | 		const groupTitle = !!group ? group.get('title') : '' | ||||||
| 		const groupId = !!group ? group.get('id') : undefined | 		const groupId = !!group ? group.get('id') : undefined | ||||||
| 		 | 		 | ||||||
|  | 		const isPrivate = !!group ? group.get('is_private') : false | ||||||
|  | 		const isMember = !!relationships ? relationships.get('member') : false | ||||||
|  | 		const unavailable = isPrivate && !isMember | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| 		return ( | 		return ( | ||||||
| 			<GroupLayout | 			<GroupLayout | ||||||
| 				title={'Group'} | 				title={'Group'} | ||||||
| @ -38,14 +45,23 @@ class GroupPage extends ImmutablePureComponent { | |||||||
| 				<PageTitle path={[groupTitle, intl.formatMessage(messages.group)]} /> | 				<PageTitle path={[groupTitle, intl.formatMessage(messages.group)]} /> | ||||||
| 				 | 				 | ||||||
| 				{ | 				{ | ||||||
| 					!!relationships && isTimeline && relationships.get('member') && | 					!!relationships && isTimeline && isMember && | ||||||
| 					<React.Fragment> | 					<React.Fragment> | ||||||
| 						<TimelineComposeBlock size={46} groupId={groupId} autoFocus /> | 						<TimelineComposeBlock size={46} groupId={groupId} autoFocus /> | ||||||
| 						<Divider /> | 						<Divider /> | ||||||
| 					</React.Fragment> | 					</React.Fragment> | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				{children} | 				{ | ||||||
|  | 					unavailable && | ||||||
|  | 					<Block> | ||||||
|  | 						<ColumnIndicator type='error' message={intl.formatMessage(messages.groupPrivate)} /> | ||||||
|  | 					</Block> | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				{ | ||||||
|  | 					!unavailable && children | ||||||
|  | 				} | ||||||
| 			</GroupLayout> | 			</GroupLayout> | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| @ -53,6 +69,7 @@ class GroupPage extends ImmutablePureComponent { | |||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
| 	group: { id: 'group', defaultMessage: 'Group' }, | 	group: { id: 'group', defaultMessage: 'Group' }, | ||||||
|  | 	groupPrivate: { id: 'group_private', defaultMessage: 'This group is private. You must request to join in order to view this group.' }, | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state, { params: { id } }) => ({ | const mapStateToProps = (state, { params: { id } }) => ({ | ||||||
|  | |||||||
| @ -48,6 +48,10 @@ import { | |||||||
|   GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS, |   GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS, | ||||||
|   GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS, |   GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS, | ||||||
|   GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS, |   GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS, | ||||||
|  |   GROUP_JOIN_REQUESTS_FETCH_SUCCESS, | ||||||
|  |   GROUP_JOIN_REQUESTS_EXPAND_SUCCESS, | ||||||
|  |   GROUP_JOIN_REQUESTS_APPROVE_SUCCESS, | ||||||
|  |   GROUP_JOIN_REQUESTS_REJECT_SUCCESS, | ||||||
| } from '../actions/groups' | } from '../actions/groups' | ||||||
| 
 | 
 | ||||||
| const initialState = ImmutableMap({ | const initialState = ImmutableMap({ | ||||||
| @ -60,6 +64,7 @@ const initialState = ImmutableMap({ | |||||||
|   mutes: ImmutableMap(), |   mutes: ImmutableMap(), | ||||||
|   groups: ImmutableMap(), |   groups: ImmutableMap(), | ||||||
|   group_removed_accounts: ImmutableMap(), |   group_removed_accounts: ImmutableMap(), | ||||||
|  |   group_join_requests: ImmutableMap(), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const setListFailed = (state, type, id) => { | const setListFailed = (state, type, id) => { | ||||||
| @ -168,6 +173,14 @@ export default function userLists(state = initialState, action) { | |||||||
|   case GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS: |   case GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS: | ||||||
|     return state.updateIn(['group_removed_accounts', action.groupId, 'items'], list => list.filterNot(item => item === action.id)); |     return state.updateIn(['group_removed_accounts', action.groupId, 'items'], list => list.filterNot(item => item === action.id)); | ||||||
|    |    | ||||||
|  |   case GROUP_JOIN_REQUESTS_FETCH_SUCCESS: | ||||||
|  |     return normalizeList(state, 'group_join_requests', action.id, action.accounts, action.next); | ||||||
|  |   case GROUP_JOIN_REQUESTS_EXPAND_SUCCESS: | ||||||
|  |     return appendToList(state, 'group_join_requests', action.id, action.accounts, action.next); | ||||||
|  |   case GROUP_JOIN_REQUESTS_APPROVE_SUCCESS: | ||||||
|  |   case GROUP_JOIN_REQUESTS_REJECT_SUCCESS: | ||||||
|  |     return state.updateIn(['group_join_requests', action.groupId, 'items'], list => list.filterNot(item => item === action.id)); | ||||||
|  |    | ||||||
|   default: |   default: | ||||||
|     return state; |     return state; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -17,6 +17,10 @@ module GroupInteractions | |||||||
|       follow_mapping(GroupAccount.where(group_id: target_group_ids, account_id: account_id, role: :moderator), :group_id) |       follow_mapping(GroupAccount.where(group_id: target_group_ids, account_id: account_id, role: :moderator), :group_id) | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     def requested_map(target_group_ids, account_id) | ||||||
|  |       follow_mapping(GroupJoinRequest.where(group_id: target_group_ids, account_id: account_id), :group_id) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def pinned?(status) |     def pinned?(status) | ||||||
|       group_pinned_statuses.where(group_id: status.group_id, status: status).exists? |       group_pinned_statuses.where(group_id: status.group_id, status: status).exists? | ||||||
|     end |     end | ||||||
|  | |||||||
| @ -36,6 +36,9 @@ class Group < ApplicationRecord | |||||||
|   has_many :group_accounts, inverse_of: :group, dependent: :destroy |   has_many :group_accounts, inverse_of: :group, dependent: :destroy | ||||||
|   has_many :accounts, through: :group_accounts |   has_many :accounts, through: :group_accounts | ||||||
|    |    | ||||||
|  |   has_many :group_join_requests, inverse_of: :group, dependent: :destroy | ||||||
|  |   has_many :join_requests, source: :account, through: :group_join_requests | ||||||
|  | 
 | ||||||
|   has_many :group_pinned_statuses, inverse_of: :group, dependent: :destroy |   has_many :group_pinned_statuses, inverse_of: :group, dependent: :destroy | ||||||
|   has_many :pinned_statuses, source: :status, through: :group_pinned_statuses |   has_many :pinned_statuses, source: :status, through: :group_pinned_statuses | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								app/models/group_join_request.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/models/group_join_request.rb
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | # == Schema Information | ||||||
|  | # | ||||||
|  | # Table name: group_join_requests | ||||||
|  | # | ||||||
|  | #  id         :bigint(8)        not null, primary key | ||||||
|  | #  account_id :bigint(8)        not null | ||||||
|  | #  group_id   :bigint(8)        not null | ||||||
|  | #  created_at :datetime         not null | ||||||
|  | #  updated_at :datetime         not null | ||||||
|  | # | ||||||
|  | 
 | ||||||
|  | class GroupJoinRequest < ApplicationRecord | ||||||
|  |   belongs_to :group | ||||||
|  |   belongs_to :account | ||||||
|  | 
 | ||||||
|  |   validates :account_id, uniqueness: { scope: :group_id } | ||||||
|  | end | ||||||
| @ -1,7 +1,7 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class GroupRelationshipsPresenter | class GroupRelationshipsPresenter | ||||||
|     attr_reader :member, :admin, :moderator |     attr_reader :member, :admin, :moderator, :requested | ||||||
|    |    | ||||||
|     def initialize(group_ids, current_account_id, **options) |     def initialize(group_ids, current_account_id, **options) | ||||||
|       @group_ids          = group_ids.map { |a| a.is_a?(Group) ? a.id : a } |       @group_ids          = group_ids.map { |a| a.is_a?(Group) ? a.id : a } | ||||||
| @ -10,6 +10,7 @@ class GroupRelationshipsPresenter | |||||||
|       @member       = Group.member_map(@group_ids, @current_account_id) |       @member       = Group.member_map(@group_ids, @current_account_id) | ||||||
|       @admin        = Group.admin_map(@group_ids, @current_account_id) |       @admin        = Group.admin_map(@group_ids, @current_account_id) | ||||||
|       @moderator    = Group.moderator_map(@group_ids, @current_account_id) |       @moderator    = Group.moderator_map(@group_ids, @current_account_id) | ||||||
|  |       @requested    = Group.requested_map(@group_ids, @current_account_id) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|    |    | ||||||
| @ -1,7 +1,7 @@ | |||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class REST::GroupRelationshipSerializer < ActiveModel::Serializer | class REST::GroupRelationshipSerializer < ActiveModel::Serializer | ||||||
|   attributes :id, :member, :admin, :moderator |   attributes :id, :member, :admin, :moderator, :requested | ||||||
| 
 | 
 | ||||||
|   def id |   def id | ||||||
|     object.id.to_s |     object.id.to_s | ||||||
| @ -19,4 +19,8 @@ class REST::GroupRelationshipSerializer < ActiveModel::Serializer | |||||||
|     instance_options[:relationships].moderator[object.id] ? true : false |     instance_options[:relationships].moderator[object.id] ? true : false | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def requested | ||||||
|  |     instance_options[:relationships].requested[object.id] ? true : false | ||||||
|  |   end | ||||||
|  | 
 | ||||||
| end | end | ||||||
|  | |||||||
| @ -440,6 +440,11 @@ Rails.application.routes.draw do | |||||||
|         resources :relationships, only: :index, controller: 'groups/relationships' |         resources :relationships, only: :index, controller: 'groups/relationships' | ||||||
|         resource :accounts, only: [:show, :create, :update, :destroy], controller: 'groups/accounts' |         resource :accounts, only: [:show, :create, :update, :destroy], controller: 'groups/accounts' | ||||||
|         resource :removed_accounts, only: [:show, :create, :destroy], controller: 'groups/removed_accounts' |         resource :removed_accounts, only: [:show, :create, :destroy], controller: 'groups/removed_accounts' | ||||||
|  |         resource :join_requests, only: [:show, :create], controller: 'groups/requests' | ||||||
|  |          | ||||||
|  |         post '/join_requests/approve', to: 'groups/requests#approve_request' | ||||||
|  |         delete '/join_requests/reject', to: 'groups/requests#reject_request' | ||||||
|  | 
 | ||||||
|         resource :pin, only: :create, controller: 'groups/pins' |         resource :pin, only: :create, controller: 'groups/pins' | ||||||
|         post :unpin, to: 'groups/pins#destroy' |         post :unpin, to: 'groups/pins#destroy' | ||||||
|       end |       end | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 mgabdev
						mgabdev