gab-social/app/javascript/gabsocial/features/group_create.js
mgabdev 6d85c76c8f Added ability to set password for groups
• Added:
- ability to set password for groups
- GroupPasswordModal
- checks for if has password
- rate limiting in rack_attack
2020-09-11 17:27:00 -05:00

444 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { defineMessages, injectIntl } from 'react-intl'
import isObject from 'lodash.isobject'
import {
changeGroupTitle,
changeGroupPassword,
changeGroupDescription,
changeGroupCoverImage,
changeGroupId,
changeGroupTags,
changeGroupCategory,
changeGroupIsPrivate,
changeGroupIsVisible,
submit,
setGroup,
resetEditor,
} from '../actions/group_editor'
import {
openModal,
closeModal,
} from '../actions/modal'
import { fetchGroup } from '../actions/groups'
import { fetchGroupCategories } from '../actions/group_categories'
import { me } from '../initial_state'
import { MODAL_PRO_UPGRADE } from '../constants'
import ColumnIndicator from '../components/column_indicator'
import Button from '../components/button'
import Divider from '../components/divider'
import Input from '../components/input'
import Text from '../components/text'
import Form from '../components/form'
import Switch from '../components/switch'
import Select from '../components/select'
import Textarea from '../components/textarea'
import FileInput from '../components/file_input'
class GroupCreate extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object
}
componentDidMount() {
const { groupId, group } = this.props
this.props.onFetchGroupCategories()
if (!group) {
if (groupId) {
this.props.onFetchGroup(groupId)
} else {
this.props.onResetEditor()
}
} else {
this.props.onSetGroup(group)
}
}
componentWillReceiveProps(nextProps) {
if (this.props.group !== nextProps.group && !!nextProps.group) {
this.props.onSetGroup(nextProps.group)
}
}
handleCoverImageChange = (e) => {
try {
this.props.onCoverImageChange(e.target.files[0])
} catch (error) {
//
}
}
handleSubmit = (e) => {
e.preventDefault()
// if not pro and not admin of group show pro upgrade modal
if (!this.props.isPro && !this.props.isAdmin) {
this.props.onOpenProUpgradeModal()
return
}
if (this.props.onClose) this.props.onClose()
this.props.onSubmit(this.context.router.history)
}
render() {
const {
group,
error,
titleValue,
descriptionValue,
passwordValue,
coverImage,
intl,
onTitleChange,
onChangeGroupPassword,
onDescriptionChange,
onChangeGroupId,
onChangeGroupTags,
onChangeGroupCategory,
onChangeGroupIsPrivate,
onChangeGroupIsVisible,
isSubmitting,
onSubmit,
idValue,
tags,
category,
isPrivate,
isVisible,
groupId,
categories,
isAdmin,
isPro,
} = this.props
if (!group && groupId) {
return <ColumnIndicator type='loading' />
} else if ((!group && error) || (groupId && !isAdmin)) {
return <ColumnIndicator type='missing' />
} else if (!isPro && !groupId) {
return <ColumnIndicator type='error' message={(
<React.Fragment>
<Text>You must be a GabPRO member to create a group.</Text>
<Button
onClick={this.props.onOpenProUpgradeModal}
className={[_s.mt15, _s.mlAuto, _s.mrAuto].join(' ')}
>
Learn more
</Button>
</React.Fragment>
)} />
}
const memberCount = group ? group.get('member_count') : 0
const hasGroupSlug = group ? !!group.get('slug') : false
let categoriesOptions = [{'title':'',value:''}]
if (categories) {
for (let i = 0; i < categories.count(); i++) {
const c = categories.get(i)
categoriesOptions.push({
title: c.get('text'),
value: c.get('id'),
})
}
}
const submitDisabled = ((!titleValue || !category || !descriptionValue) && !groupId) || isSubmitting
return (
<Form onSubmit={onSubmit}>
<Input
id='group-title'
title={`${intl.formatMessage(messages.title)} *`}
value={titleValue}
onChange={onTitleChange}
disabled={isSubmitting}
placeholder={intl.formatMessage(messages.titlePlaceholder)}
isRequired
/>
<Divider isInvisible />
{
memberCount >= 50 && !hasGroupSlug &&
<React.Fragment>
<Input
id='group-id'
title={intl.formatMessage(messages.idTitle)}
value={idValue}
onChange={onChangeGroupId}
disabled={isSubmitting}
/>
<Text className={[_s.mt5, _s.pl15]} size='small' color='secondary'>
{
!!idValue &&
<b>g/{idValue}&nbsp;&nbsp;</b>
}
{intl.formatMessage(messages.idDescription)}
</Text>
<Divider isInvisible />
</React.Fragment>
}
<Textarea
title={`${intl.formatMessage(messages.description)} *`}
value={descriptionValue}
onChange={onDescriptionChange}
placeholder={intl.formatMessage(messages.descriptionPlaceholder)}
disabled={isSubmitting}
isRequired
/>
<Divider isInvisible />
<div className={_s.d}>
<Text className={[_s.pl15, _s.mb10].join(' ')} size='small' weight='medium' color='secondary'>
{intl.formatMessage(messages.categoryTitle)} *
</Text>
<Select
value={category}
onChange={onChangeGroupCategory}
options={categoriesOptions}
/>
<Text className={[_s.mt5, _s.pl15].join(' ')} size='small' color='tertiary'>
{intl.formatMessage(messages.categoryDescription)}
</Text>
<Divider isInvisible />
</div>
<Input
id='group-tags'
title={intl.formatMessage(messages.tagsTitle)}
value={tags}
onChange={onChangeGroupTags}
disabled={isSubmitting}
/>
<Text className={[_s.mt5, _s.pl15, _s.mb15, _s.pb5].join(' ')} size='small' color='tertiary'>
{intl.formatMessage(messages.tagsDescription)}
</Text>
<Divider />
<FileInput
disabled={isSubmitting}
id='group-cover-photo'
title={intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)}
onChange={this.handleCoverImageChange}
file={group ? group.get('cover_image_url') : undefined}
width='340px'
height='145px'
isBordered
/>
<Text className={[_s.mt5, _s.pl15, _s.mb15, _s.pb5].join(' ')} size='small' color='tertiary'>
{intl.formatMessage(messages.coverImageDescription)}
</Text>
<Divider />
<Input
id='group-password'
title={intl.formatMessage(messages.passwordTitle)}
value={passwordValue}
onChange={onChangeGroupPassword}
disabled={isSubmitting}
/>
<Text className={[_s.mt5, _s.pl15, _s.mb15, _s.pb5].join(' ')} size='small' color='tertiary'>
{intl.formatMessage(messages.passwordDescription)}
</Text>
<Divider />
<div className={[_s.d, _s.pl15].join(' ')}>
<Switch
label={'Private'}
id='group-isprivate'
checked={isPrivate}
onChange={onChangeGroupIsPrivate}
labelProps={{
size: 'small',
weight: 'medium',
color: 'secondary',
}}
/>
<Text className={_s.mt5} size='small' color='tertiary'>
{intl.formatMessage(messages.isPrivateDescription)}
</Text>
<Divider isInvisible />
<Switch
label={'Visible'}
id='group-isvisible'
checked={isVisible}
onChange={onChangeGroupIsVisible}
labelProps={{
size: 'small',
weight: 'medium',
color: 'secondary',
}}
/>
<Text className={_s.mt5} size='small' color='tertiary'>
{intl.formatMessage(messages.isVisibleDescription)}
</Text>
</div>
<Divider isInvisible />
<Button
isDisabled={submitDisabled}
onClick={this.handleSubmit}
>
<Text color='inherit' align='center' weight='medium'>
{intl.formatMessage(!!group ? messages.update : messages.create)}
</Text>
</Button>
</Form>
)
}
}
const messages = defineMessages({
title: { id: 'groups.form.title', defaultMessage: 'Title' },
idTitle: { id: 'groups.form.id_title', defaultMessage: 'Unique id' },
idDescription: { id: 'groups.form.id_description', defaultMessage: 'A unique id that links to this group. (Cannot be changed)' },
tagsTitle: { id: 'groups.form.tags_title', defaultMessage: 'Tags' },
tagsDescription: { id: 'groups.form.tags_description', defaultMessage: '(Optional) Add tags seperated by commas to increase group visibility' },
passwordTitle: { id: 'groups.form.password_title', defaultMessage: 'Password' },
passwordDescription: { id: 'groups.form.password_description', defaultMessage: '(Optional) Add a password to restrict access to this group. This password is NOT encrypted and is only visible to group admins.' },
categoryTitle: { id: 'groups.form.category_title', defaultMessage: 'Category' },
categoryDescription: { id: 'groups.form.category_description', defaultMessage: 'Add a general category for your group' },
description: { id: 'groups.form.description', defaultMessage: 'Description' },
coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Cover image' },
coverImageDescription: { id: 'groups.form.coverImage_description', defaultMessage: '(Optional) Max: 5MB. Accepted image types: .jpg, .png' },
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Cover image selected' },
create: { id: 'groups.form.create', defaultMessage: 'Create group' },
update: { id: 'groups.form.update', defaultMessage: 'Update group' },
titlePlaceholder: { id: 'groups.form.title_placeholder', defaultMessage: 'New group title...' },
descriptionPlaceholder: { id: 'groups.form.description_placeholder', defaultMessage: 'This group is about...' },
isPrivateDescription: { id: 'groups.form.is_private_description', defaultMessage: 'Only members can see group posts.' },
isVisibleDescription: { id: 'groups.form.is_visible_description', defaultMessage: 'Anyone can find a visible group in search and other places on Gab.' },
})
const mapStateToProps = (state, { params }) => {
const groupId = isObject(params) ? params['id'] : null
const group = state.getIn(['groups', groupId])
let isAdmin = false
if (groupId) {
const relationships = state.getIn(['group_relationships', groupId])
if (relationships) {
isAdmin = relationships.get('admin')
}
}
return {
group,
groupId,
isAdmin,
error: (groupId && !group) || (group && !isAdmin),
titleValue: state.getIn(['group_editor', 'title']),
passwordValue: state.getIn(['group_editor', 'password']),
descriptionValue: state.getIn(['group_editor', 'description']),
coverImage: state.getIn(['group_editor', 'coverImage']),
isSubmitting: state.getIn(['group_editor', 'isSubmitting']),
idValue: state.getIn(['group_editor', 'id']),
tags: state.getIn(['group_editor', 'tags']),
category: state.getIn(['group_editor', 'category']),
isPrivate: state.getIn(['group_editor', 'isPrivate']),
isVisible: state.getIn(['group_editor', 'isVisible']),
categories: state.getIn(['group_categories', 'items']),
isPro: state.getIn(['accounts', me, 'is_pro']),
}
}
const mapDispatchToProps = (dispatch) => ({
onTitleChange(value) {
dispatch(changeGroupTitle(value))
},
onDescriptionChange(value) {
dispatch(changeGroupDescription(value))
},
onChangeGroupPassword(value) {
dispatch(changeGroupPassword(value))
},
onCoverImageChange(imageData) {
dispatch(changeGroupCoverImage(imageData))
},
onChangeGroupId(value) {
dispatch(changeGroupId(value))
},
onChangeGroupTags(value) {
dispatch(changeGroupTags(value))
},
onChangeGroupCategory(e) {
dispatch(changeGroupCategory(e.target.value))
},
onChangeGroupIsPrivate(checked) {
dispatch(changeGroupIsPrivate(checked))
},
onChangeGroupIsVisible(checked) {
dispatch(changeGroupIsVisible(checked))
},
onResetEditor() {
dispatch(resetEditor())
},
onSetGroup(group) {
dispatch(setGroup(group))
},
onSubmit(routerHistory) {
dispatch(submit(routerHistory))
dispatch(closeModal())
},
onFetchGroup(groupId) {
dispatch(fetchGroup(groupId))
},
onFetchGroupCategories() {
dispatch(fetchGroupCategories())
},
onOpenProUpgradeModal() {
dispatch(openModal(MODAL_PRO_UPGRADE))
},
})
GroupCreate.propTypes = {
group: ImmutablePropTypes.map,
titleValue: PropTypes.string.isRequired,
descriptionValue: PropTypes.string.isRequired,
coverImage: PropTypes.object,
intl: PropTypes.object.isRequired,
onTitleChange: PropTypes.func.isRequired,
onDescriptionChange: PropTypes.func.isRequired,
onChangeGroupId: PropTypes.func.isRequired,
onChangeGroupTags: PropTypes.func.isRequired,
onChangeGroupPassword: PropTypes.func.isRequired,
onChangeGroupCategory: PropTypes.func.isRequired,
onChangeGroupIsPrivate: PropTypes.func.isRequired,
onChangeGroupIsVisible: PropTypes.func.isRequired,
onFetchGroup: PropTypes.func.isRequired,
onFetchGroupCategories: PropTypes.func.isRequired,
onResetEditor: PropTypes.func.isRequired,
onSetGroup: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onOpenProUpgradeModal: PropTypes.func.isRequired,
isSubmitting: PropTypes.bool,
isAdmin: PropTypes.bool,
onClose: PropTypes.func,
idValue: PropTypes.string.isRequired,
tags: PropTypes.string.isRequired,
category: PropTypes.string.isRequired,
isPrivate: PropTypes.bool.isRequired,
isVisible: PropTypes.bool.isRequired,
categories: ImmutablePropTypes.list.isRequired,
}
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(GroupCreate))