Added follow requests to notifications page

• Added:
- follow requests to notifications

• Updated:
- ui.js will mount to listen for path for follow_requests to set filter
- notification action expandNotifications to not request if filter is follow_request
- order of notification_filters
- AccountAuthorize
- FollowRequest page
This commit is contained in:
mgabdev 2020-06-07 13:27:08 -04:00
parent 3bea8213ce
commit 66fc8269cc
10 changed files with 166 additions and 224 deletions

@ -153,7 +153,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
const notifications = getState().get('notifications')
const isLoadingMore = !!maxId
if (notifications.get('isLoading')) {
if (notifications.get('isLoading') || activeFilter === 'follow_requests') {
done();
return;
}

@ -0,0 +1,61 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { authorizeFollowRequest, rejectFollowRequest } from '../actions/accounts'
import { makeGetAccount } from '../selectors'
import Account from './account'
const messages = defineMessages({
authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
})
const makeMapStateToProps = (state, props) => ({
account: makeGetAccount()(state, props.id),
})
const mapDispatchToProps = (dispatch, { id }) => ({
onAuthorize() {
dispatch(authorizeFollowRequest(id))
},
onReject() {
dispatch(rejectFollowRequest(id))
},
})
export default
@connect(makeMapStateToProps, mapDispatchToProps)
@injectIntl
class AccountAuthorize extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
onAuthorize: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired,
}
render () {
const {
intl,
account,
onAuthorize,
onReject,
} = this.props
// <Button title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} />
// <Button title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} />
return (
<Account
id={account.get('id')}
dismissAction={onReject}
onActionClick={onAuthorize}
actionIcon='add'
showDismiss
withBio
/>
)
}
}

@ -97,6 +97,7 @@ export const NOTIFICATION_FILTERS = [
'mention',
'favourite',
'reblog',
'poll',
'follow',
'poll',
'follow_requests',
]

@ -0,0 +1,90 @@
import { FormattedMessage } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import debounce from 'lodash.debounce'
import { me } from '../initial_state'
import { fetchFollowRequests, expandFollowRequests } from '../actions/accounts'
import AccountAuthorize from '../components/account_authorize'
import Block from '../components/block'
import ScrollableList from '../components/scrollable_list'
import Text from '../components/text'
const mapStateToProps = (state) => ({
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
isLoading: state.getIn(['user_lists', 'follow_requests', 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
locked: !!state.getIn(['accounts', me, 'locked']),
})
const mapDispatchToProps = (dispatch) => ({
onFetchFollowRequests() {
dispatch(fetchFollowRequests())
},
onExpandFollowRequests() {
dispatch(expandFollowRequests())
},
})
export default
@connect(mapStateToProps, mapDispatchToProps)
class FollowRequests extends ImmutablePureComponent {
static propTypes = {
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
locked: PropTypes.bool,
onFetchFollowRequests: PropTypes.func.isRequired,
onExpandFollowRequests: PropTypes.func.isRequired,
}
componentWillMount () {
this.props.onFetchFollowRequests()
}
handleLoadMore = debounce(() => {
this.props.onExpandFollowRequests()
}, 300, { leading: true })
render () {
const {
accountIds,
hasMore,
isLoading,
locked,
} = this.props
const unlockedPrependMessage = locked ? null : (
<div className={[_s.default, _s.px15, _s.py15].join(' ')}>
<Text>
<FormattedMessage
id='follow_requests.unlocked_explanation'
defaultMessage='Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.'
values={{ domain: 'Gab.com' }}
/>
</Text>
</div>
)
return (
<Block>
{unlockedPrependMessage}
<ScrollableList
scrollKey='follow_requests'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
isLoading={isLoading}
emptyMessage={<FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />}
>
{
!!accountIds && accountIds.map(id =>
<AccountAuthorize key={id} id={id} />
)
}
</ScrollableList>
</Block>
)
}
}

@ -1,70 +0,0 @@
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { authorizeFollowRequest, rejectFollowRequest } from '../../../../actions/accounts';
import { makeGetAccount } from '../../../../selectors';
import Button from '../../../../components/button'
import Avatar from '../../../../components/avatar';
import DisplayName from '../../../../components/display_name';
const messages = defineMessages({
authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
});
const makeMapStateToProps = () => ({
account: makeGetAccount()(state, props.id),
})
const mapDispatchToProps = (dispatch, { id }) => ({
onAuthorize() {
dispatch(authorizeFollowRequest(id));
},
onReject() {
dispatch(rejectFollowRequest(id));
},
});
export default
@connect(makeMapStateToProps, mapDispatchToProps)
@injectIntl
class AccountAuthorize extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
onAuthorize: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
render () {
const { intl, account, onAuthorize, onReject } = this.props;
const content = { __html: account.get('note_emojified') };
return (
<div className='account-authorize__wrapper'>
<div className='account-authorize'>
<Button href={`/${account.get('acct')}`} to={`/${account.get('acct')}`} className='account-authorize__display-name'>
<div className='account-authorize__avatar'>
<Avatar account={account} size={48} />
</div>
<DisplayName account={account} />
</Button>
<div className='account__header__content' dangerouslySetInnerHTML={content} />
</div>
<div className='account--panel'>
<div className='account--panel__button'>
<Button title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} />
</div>
<div className='account--panel__button'>
<Button title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} />
</div>
</div>
</div>
);
}
}

@ -1,72 +0,0 @@
.account-authorize {
padding: 14px 10px;
&__avatar {
float: left;
margin-right: 10px;
}
&__display-name {
display: block;
color: $secondary-text-color;
margin-bottom: 15px;
line-height: 24px;
overflow: hidden;
strong,
span {
display: block;
@include text-overflow;
}
strong {
font-size: 16px;
color: $primary-text-color;
}
&:hover strong {
text-decoration: underline;
}
}
}
.account__header__content {
color: $darker-text-color;
overflow: hidden;
word-break: normal;
word-wrap: break-word;
@include text-sizing(14px, 400);
p {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
a {
color: inherit;
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
}
.account--panel {
background: lighten($ui-base-color, 4%);
border-top: 1px solid lighten($ui-base-color, 8%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
display: flex;
flex-direction: row;
padding: 10px 0;
}
.account--panel__button {
flex: 1 1 auto;
text-align: center;
}

@ -1 +0,0 @@
export { default } from './account_authorize'

@ -1,77 +0,0 @@
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import debounce from 'lodash.debounce'
import { me } from '../../initial_state'
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'
import AccountAuthorize from './components/account_authorize'
import ScrollableList from '../../components/scrollable_list'
const messages = defineMessages({
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
})
const mapStateToProps = (state) => ({
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
isLoading: state.getIn(['user_lists', 'follow_requests', 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
locked: !!state.getIn(['accounts', me, 'locked']),
domain: state.getIn(['meta', 'domain']),
})
export default
@connect(mapStateToProps)
@injectIntl
class FollowRequests extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
hasMore: PropTypes.bool,
locked: PropTypes.bool,
domain: PropTypes.string,
isLoading: PropTypes.bool,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
}
componentWillMount () {
this.props.dispatch(fetchFollowRequests())
}
handleLoadMore = debounce(() => {
this.props.dispatch(expandFollowRequests())
}, 300, { leading: true })
render () {
const { intl, accountIds, hasMore, locked, domain, isLoading } = this.props
// : todo :
const unlockedPrependMessage = locked ? null : (
<div className='follow_requests-unlocked_explanation'>
<FormattedMessage
id='follow_requests.unlocked_explanation'
defaultMessage='Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.'
values={{ domain: domain }}
/>
</div>
);
return (
<ScrollableList
scrollKey='follow_requests'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
isLoading={isLoading}
emptyMessage={<FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />}
>
{
!!accountIds && accountIds.map(id =>
<AccountAuthorize key={id} id={id} />
)
}
</ScrollableList>
);
}
}

@ -1 +0,0 @@
export { default } from './follow_requests'

@ -1,6 +1,7 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl'
import { setFilter } from '../actions/notifications'
import { me } from '../initial_state'
import { NOTIFICATION_FILTERS } from '../constants'
import PageTitle from '../features/ui/util/page_title'
import LinkFooter from '../components/link_footer'
@ -23,6 +24,7 @@ const messages = defineMessages({
const mapStateToProps = (state) => ({
selectedFilter: state.getIn(['notifications', 'filter', 'active']),
notificationCount: state.getIn(['notifications', 'unread']),
locked: !!state.getIn(['accounts', me, 'locked']),
})
const mapDispatchToProps = (dispatch) => ({
@ -46,6 +48,7 @@ class NotificationsPage extends PureComponent {
notificationCount: PropTypes.number.isRequired,
setFilter: PropTypes.func.isRequired,
selectedFilter: PropTypes.string.isRequired,
locked: PropTypes.bool,
}
onChangeActiveFilter(notificationType) {
@ -53,6 +56,8 @@ class NotificationsPage extends PureComponent {
if (notificationType === 'all') {
this.context.router.history.push('/notifications')
} else if (notificationType === 'follow_requests') {
this.context.router.history.push(`/notifications/follow_requests`)
} else {
this.context.router.history.push(`/notifications?view=${notificationType}`)
}
@ -64,9 +69,15 @@ class NotificationsPage extends PureComponent {
intl,
notificationCount,
selectedFilter,
locked
} = this.props
const tabs = NOTIFICATION_FILTERS.map((filter) => ({
let filters = NOTIFICATION_FILTERS
if (!locked && filters.indexOf('follow_requests') > -1) {
filters.splice(filters.indexOf('follow_requests'))
}
const tabs = filters.map((filter) => ({
title: intl.formatMessage(messages[filter]),
onClick: () => this.onChangeActiveFilter(filter),
active: selectedFilter.toLowerCase() === filter.toLowerCase(),