Added GroupActionButton component

• Added:
- GroupActionButton component for handling group + relationships for joining, requesting, leaving groups

• Updated:
- Components where group actions exist
This commit is contained in:
mgabdev 2020-09-11 17:10:31 -05:00
parent fb8c705ebf
commit ed58ca4fe9
4 changed files with 182 additions and 111 deletions

View File

@ -0,0 +1,157 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { injectIntl, defineMessages } from 'react-intl'
import {
joinGroup,
leaveGroup,
} from '../actions/groups'
import { openModal } from '../actions/modal'
import { me } from '../initial_state'
import {
CX,
MODAL_GROUP_PASSWORD,
} from '../constants'
import Button from './button'
import Text from './text'
class GroupActionButton extends ImmutablePureComponent {
handleToggleMembership = () => {
const { group, relationships } = this.props
if (!group || !relationships) return false
const groupId = group.get('id')
const hasPassword = group.get('has_password')
const isMember = relationships.get('member')
const isRequested = relationships.get('requested')
if (isMember || isRequested) {
this.props.onLeaveGroup(groupId)
} else {
if (hasPassword) {
this.props.onOpenGroupPasswordModal(group)
} else {
this.props.onJoinGroup(groupId)
}
}
}
render() {
const {
group,
intl,
relationships,
size,
} = this.props
if (!group || !relationships) return null
let buttonText = ''
let buttonOptions = {}
const isPrivate = group.get('is_private')
const isMember = relationships.get('member')
const isRequested = relationships.get('requested')
if (isRequested) {
buttonText = intl.formatMessage(messages.requested)
buttonOptions = {
color: 'primary',
backgroundColor: 'tertiary',
}
} else if (isMember) {
buttonText = intl.formatMessage(messages.member)
buttonOptions = {
color: 'white',
backgroundColor: 'brand',
}
} else {
if (isPrivate && !isRequested) {
buttonText = intl.formatMessage(messages.request)
} else {
buttonText = intl.formatMessage(messages.join)
}
buttonOptions = {
color: 'brand',
backgroundColor: 'none',
isOutline: true,
}
}
const isSmall = size === 'small'
const textClassName = isSmall ? null : _s.px10
const textSize = isSmall ? 'small' : 'medium'
const textWeight = isSmall ? 'normal' : 'bold'
const btnClasses = CX({
px10: isSmall,
minW76PX: isSmall,
jcCenter: 1,
aiCenter: 1,
})
return (
<Button
{...buttonOptions}
onClick={this.handleToggleMembership}
className={btnClasses}
isNarrow={isSmall}
>
<Text
color='inherit'
align='center'
weight={textWeight}
size={textSize}
className={textClassName}
>
{buttonText}
</Text>
</Button>
)
}
}
const messages = defineMessages({
join: { id: 'groups.join', defaultMessage: 'Join group' },
request: { id: 'groups.request_join', defaultMessage: 'Request to Join' },
requested: { id: 'groups.requested', defaultMessage: 'Requested' },
member: { id: 'groups.member', defaultMessage: 'Member' },
})
const mapDispatchToProps = (dispatch) => ({
onJoinGroup(groupId) {
dispatch(joinGroup(groupId))
},
onLeaveGroup(groupId) {
dispatch(leaveGroup(groupId))
},
onOpenGroupPasswordModal(group) {
dispatch(openModal(MODAL_GROUP_PASSWORD, {
group,
}))
},
})
GroupActionButton.propTypes = {
group: ImmutablePropTypes.map,
relationships: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
size: PropTypes.oneOf([
'small',
'normal',
]),
onJoin: PropTypes.func.isRequired,
onLeave: PropTypes.func.isRequired,
onOpenGroupPasswordModal: PropTypes.func.isRequired,
}
GroupActionButton.defaultProps = {
size: 'normal',
}
export default injectIntl(connect(null, mapDispatchToProps)(GroupActionButton))

View File

@ -4,9 +4,7 @@ import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import { openPopover, closePopover } from '../actions/popover' import { openPopover } from '../actions/popover'
import { openModal } from '../actions/modal'
import { joinGroup, leaveGroup } from '../actions/groups'
import { import {
addShortcut, addShortcut,
removeShortcut, removeShortcut,
@ -17,6 +15,7 @@ import {
} from '../constants' } from '../constants'
import { me } from '../initial_state' import { me } from '../initial_state'
import Responsive from '../features/ui/util/responsive_component' import Responsive from '../features/ui/util/responsive_component'
import GroupActionButton from './group_action_button'
import Button from './button' import Button from './button'
import Block from './block' import Block from './block'
import Heading from './heading' import Heading from './heading'
@ -28,11 +27,6 @@ import Text from './text'
class GroupHeader extends ImmutablePureComponent { class GroupHeader extends ImmutablePureComponent {
handleOnToggleMembership = () => {
const { group, relationships } = this.props
this.props.onToggleMembership(group, relationships);
}
handleOnOpenGroupOptions = () => { handleOnOpenGroupOptions = () => {
const { relationships } = this.props const { relationships } = this.props
const isAdmin = !!relationships ? relationships.get('admin') : false const isAdmin = !!relationships ? relationships.get('admin') : false
@ -75,31 +69,13 @@ class GroupHeader extends ImmutablePureComponent {
const title = !!group ? group.get('title') : undefined const title = !!group ? group.get('title') : undefined
const slug = !!group ? !!group.get('slug') ? `g/${group.get('slug')}` : undefined : undefined const slug = !!group ? !!group.get('slug') ? `g/${group.get('slug')}` : undefined : undefined
const isPrivate = !!group ? group.get('is_private') : false const isPrivate = !!group ? group.get('is_private') : false
const isAdminOrMod = !!relationships ? (relationships.get('admin') || relationships.get('moderator')) : false
let isAdminOrMod = false
let actionButtonTitle
let actionButtonOptions = {}
if (relationships) {
isAdminOrMod = relationships.get('admin') || relationships.get('moderator')
const isMember = relationships.get('member')
actionButtonTitle = intl.formatMessage(isMember ? messages.member : messages.join)
actionButtonOptions = {
isOutline: !isMember,
backgroundColor: isMember ? 'brand' : 'none',
color: isMember ? 'white' : 'brand',
}
}
const tabs = !group ? [] : [ const tabs = !group ? [] : [
{ {
to: `/groups/${group.get('id')}`, to: `/groups/${group.get('id')}`,
title: 'Timeline', title: 'Timeline',
}, },
// {
// to: `/groups/${group.get('id')}/media`,
// title: 'Media',
// },
] ]
if (isAdminOrMod && group) { if (isAdminOrMod && group) {
@ -146,23 +122,15 @@ class GroupHeader extends ImmutablePureComponent {
{ {
!!me && !!me &&
<div className={[_s.d, _s.flexRow, _s.jcCenter, _s.aiCenter, _s.mt5, _s.pb15, _s.pt5, _s.h100PC, _s.borderBottom1PX, _s.borderColorSecondary, _s.px15].join(' ')}> <div className={[_s.d, _s.flexRow, _s.jcCenter, _s.aiCenter, _s.mt5, _s.pb15, _s.pt5, _s.h100PC, _s.borderBottom1PX, _s.borderColorSecondary, _s.px15].join(' ')}>
{ <GroupActionButton
!!actionButtonTitle && group={group}
<Button relationships={relationships}
className={_s.mr5} />
onClick={this.handleOnToggleMembership}
{...actionButtonOptions}
>
<Text color='inherit' size='medium' weight='bold' className={_s.px10}>
{actionButtonTitle}
</Text>
</Button>
}
<Button <Button
color='primary' color='primary'
backgroundColor='tertiary' backgroundColor='tertiary'
className={[_s.px10, _s.mr5].join(' ')} className={[_s.px10, _s.mr5, _s.ml5].join(' ')}
icon='ellipsis' icon='ellipsis'
onClick={this.handleOnOpenGroupOptions} onClick={this.handleOnOpenGroupOptions}
buttonRef={this.setInfoBtn} buttonRef={this.setInfoBtn}
@ -236,18 +204,12 @@ class GroupHeader extends ImmutablePureComponent {
/> />
</div> </div>
} }
{
!!actionButtonTitle && <GroupActionButton
<Button group={group}
className={_s.mr5} relationships={relationships}
onClick={this.handleOnToggleMembership} />
{...actionButtonOptions}
>
<Text color='inherit' size='medium' weight='bold' className={_s.px10}>
{actionButtonTitle}
</Text>
</Button>
}
</div> </div>
</div> </div>
</div> </div>
@ -262,8 +224,6 @@ class GroupHeader extends ImmutablePureComponent {
} }
const messages = defineMessages({ const messages = defineMessages({
join: { id: 'groups.join', defaultMessage: 'Join group' },
member: { id: 'groups.member', defaultMessage: 'Member' },
removed_accounts: { id: 'groups.removed_accounts', defaultMessage: 'Removed Accounts' }, removed_accounts: { id: 'groups.removed_accounts', defaultMessage: 'Removed Accounts' },
group_archived: { id: 'group.detail.archived_group', defaultMessage: 'Archived group' }, group_archived: { id: 'group.detail.archived_group', defaultMessage: 'Archived group' },
group_admin: { id: 'groups.detail.role_admin', defaultMessage: 'You\'re an admin' } group_admin: { id: 'groups.detail.role_admin', defaultMessage: 'You\'re an admin' }
@ -280,13 +240,6 @@ const mapStateToProps = (state, { group }) => {
const mapDispatchToProps = (dispatch, { intl }) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({
onToggleMembership(group, relationships) {
if (relationships.get('member')) {
dispatch(leaveGroup(group.get('id')))
} else {
dispatch(joinGroup(group.get('id')))
}
},
onOpenGroupOptions(targetRef, group, options) { onOpenGroupOptions(targetRef, group, options) {
dispatch(openPopover('GROUP_OPTIONS', { dispatch(openPopover('GROUP_OPTIONS', {
targetRef, targetRef,
@ -311,7 +264,6 @@ GroupHeader.propTypes = {
isShortcut: PropTypes.bool.isRequired, isShortcut: PropTypes.bool.isRequired,
isXS: PropTypes.bool, isXS: PropTypes.bool,
onAddShortcut: PropTypes.func.isRequired, onAddShortcut: PropTypes.func.isRequired,
onToggleMembership: PropTypes.func.isRequired,
onRemoveShortcut: PropTypes.func.isRequired, onRemoveShortcut: PropTypes.func.isRequired,
onOpenGroupOptions: PropTypes.func.isRequired, onOpenGroupOptions: PropTypes.func.isRequired,
relationships: ImmutablePropTypes.map, relationships: ImmutablePropTypes.map,

View File

@ -5,7 +5,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import { joinGroup, leaveGroup } from '../actions/groups'
import { CX } from '../constants' import { CX } from '../constants'
import { PLACEHOLDER_MISSING_HEADER_SRC } from '../constants' import { PLACEHOLDER_MISSING_HEADER_SRC } from '../constants'
import { shortNumberFormat } from '../utils/numbers' import { shortNumberFormat } from '../utils/numbers'
@ -13,26 +12,10 @@ import Button from './button'
import Image from './image' import Image from './image'
import Text from './text' import Text from './text'
import Dummy from './dummy' import Dummy from './dummy'
import GroupActionButton from './group_action_button'
class GroupListItem extends ImmutablePureComponent { class GroupListItem extends ImmutablePureComponent {
state = {
hovering: false,
}
handleOnToggleMembership = () => {
const { group, relationships } = this.props
this.props.onToggleMembership(group.get('id'), relationships)
}
handleOnMouseEnter = () => {
this.setState({ hovering: true })
}
handleOnMouseLeave = () => {
this.setState({ hovering: false })
}
render() { render() {
const { const {
group, group,
@ -43,7 +26,6 @@ class GroupListItem extends ImmutablePureComponent {
isStatic, isStatic,
relationships, relationships,
} = this.props } = this.props
const { hovering } = this.state
if (!group) return null if (!group) return null
@ -63,6 +45,7 @@ class GroupListItem extends ImmutablePureComponent {
borderBottom1PX: !isLast, borderBottom1PX: !isLast,
flexRow: 1, flexRow: 1,
py5: 1, py5: 1,
px5: isAddable,
w100PC: 1, w100PC: 1,
}) })
@ -71,14 +54,12 @@ class GroupListItem extends ImmutablePureComponent {
flexRow: 1, flexRow: 1,
noUnderline: 1, noUnderline: 1,
w100PC: 1, w100PC: 1,
maxW100PC86PX: isAddable, flexGrow1: isAddable,
flexShrink1: isAddable,
}) })
const coverSrc = group.get('cover_image_url') || '' const coverSrc = group.get('cover_image_url') || ''
const coverMissing = coverSrc.indexOf(PLACEHOLDER_MISSING_HEADER_SRC) > -1 || !coverSrc const coverMissing = coverSrc.indexOf(PLACEHOLDER_MISSING_HEADER_SRC) > -1 || !coverSrc
const isMember = !!relationships && relationships.get('member')
const addButtonColor = isMember ? hovering ? 'danger' : 'tertiary' : 'brand'
const addButtonTitle = isMember ? hovering ? 'Leave' : 'Member' : 'Join'
const Wrapper = !isStatic ? NavLink : Dummy const Wrapper = !isStatic ? NavLink : Dummy
@ -114,20 +95,11 @@ class GroupListItem extends ImmutablePureComponent {
{ {
isAddable && isAddable &&
<div className={[_s.d, _s.jcCenter, _s.flexGrow1].join(' ')}> <div className={[_s.d, _s.jcCenter, _s.flexGrow1].join(' ')}>
{ <GroupActionButton
relationships && group={group}
<Button relationships={relationships}
isNarrow size='small'
color='white' />
className={[_s.px10, _s.w76PX].join(' ')}
backgroundColor={addButtonColor}
onClick={this.handleOnToggleMembership}
onMouseEnter={this.handleOnMouseEnter}
onMouseLeave={this.handleOnMouseLeave}
>
<Text size='small' color='inherit' align='center'>{addButtonTitle}</Text>
</Button>
}
</div> </div>
} }
</div> </div>
@ -145,23 +117,12 @@ const mapStateToProps = (state, { id }) => ({
relationships: state.getIn(['group_relationships', id]), relationships: state.getIn(['group_relationships', id]),
}) })
const mapDispatchToProps = (dispatch) => ({
onToggleMembership(groupId, relationships) {
if (relationships.get('member')) {
dispatch(leaveGroup(groupId))
} else {
dispatch(joinGroup(groupId))
}
},
})
GroupListItem.propTypes = { GroupListItem.propTypes = {
group: ImmutablePropTypes.map, group: ImmutablePropTypes.map,
isAddable: PropTypes.bool, isAddable: PropTypes.bool,
isHidden: PropTypes.bool, isHidden: PropTypes.bool,
isLast: PropTypes.bool, isLast: PropTypes.bool,
isStatic: PropTypes.bool, isStatic: PropTypes.bool,
onToggleMembership: PropTypes.func.isRequired,
relationships: ImmutablePropTypes.map, relationships: ImmutablePropTypes.map,
} }
@ -169,4 +130,4 @@ GroupListItem.defaultProps = {
isLast: false, isLast: false,
} }
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(GroupListItem)) export default injectIntl(connect(mapStateToProps)(GroupListItem))

View File

@ -516,6 +516,7 @@ pre {
.maxW100PC42PX { max-width: calc(100% - 42px); } .maxW100PC42PX { max-width: calc(100% - 42px); }
.minW330PX { min-width: 330px; } .minW330PX { min-width: 330px; }
.minW76PX { min-width: 76px; }
.minW20PX { min-width: 20px; } .minW20PX { min-width: 20px; }
.minW14PX { min-width: 14px; } .minW14PX { min-width: 14px; }