diff --git a/app/javascript/gabsocial/actions/group_editor.js b/app/javascript/gabsocial/actions/group_editor.js new file mode 100644 index 00000000..23231b34 --- /dev/null +++ b/app/javascript/gabsocial/actions/group_editor.js @@ -0,0 +1,78 @@ +import api from '../api'; +import { me } from 'gabsocial/initial_state'; + +export const GROUP_CREATE_REQUEST = 'GROUP_CREATE_REQUEST'; +export const GROUP_CREATE_SUCCESS = 'GROUP_CREATE_SUCCESS'; +export const GROUP_CREATE_FAIL = 'GROUP_CREATE_FAIL'; + +export const GROUP_UPDATE_REQUEST = 'GROUP_UPDATE_REQUEST'; +export const GROUP_UPDATE_SUCCESS = 'GROUP_UPDATE_SUCCESS'; +export const GROUP_UPDATE_FAIL = 'GROUP_UPDATE_FAIL'; + +export const GROUP_EDITOR_VALUE_CHANGE = 'GROUP_EDITOR_VALUE_CHANGE'; +export const GROUP_EDITOR_RESET = 'GROUP_EDITOR_RESET'; +export const GROUP_EDITOR_SETUP = 'GROUP_EDITOR_SETUP'; + +export const submit = (routerHistory) => (dispatch, getState) => { + const groupId = getState().getIn(['group_editor', 'groupId']); + const title = getState().getIn(['group_editor', 'title']); + const description = getState().getIn(['group_editor', 'description']); + const coverImage = getState().getIn(['group_editor', 'coverImage']); + + if (groupId === null) { + dispatch(create(title, description, coverImage, routerHistory)); + } else { + dispatch(update(groupId, title, description, coverImage, routerHistory)); + } +}; + + +export const create = (title, description, coverImage, routerHistory) => (dispatch, getState) => { + if (!me) return; + + dispatch(createRequest()); + + const formData = new FormData(); + formData.append('title', title); + formData.append('description', description); + debugger; + if (coverImage !== null) { + formData.append('cover_image', coverImage); + } + + api(getState).post('/api/v1/groups', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(({ data }) => { + dispatch(createSuccess(data)); + routerHistory.push(`/groups/${data.id}`); + }).catch(err => dispatch(createFail(err))); + }; + + +export const createRequest = id => ({ + type: GROUP_CREATE_REQUEST, + id, +}); + +export const createSuccess = group => ({ + type: GROUP_CREATE_SUCCESS, + group, +}); + +export const createFail = error => ({ + type: GROUP_CREATE_FAIL, + error, +}); + +export const changeValue = (field, value) => ({ + type: GROUP_EDITOR_VALUE_CHANGE, + field, + value, +}); + +export const reset = () => ({ + type: GROUP_EDITOR_RESET +}); + +export const setUp = (group) => ({ + type: GROUP_EDITOR_SETUP, + group, +}); \ No newline at end of file diff --git a/app/javascript/gabsocial/features/groups/create/index.js b/app/javascript/gabsocial/features/groups/create/index.js index b7f3a41b..e10a7c0b 100644 --- a/app/javascript/gabsocial/features/groups/create/index.js +++ b/app/javascript/gabsocial/features/groups/create/index.js @@ -1,78 +1,121 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { changeListEditorTitle, submitListEditor } from '../../../actions/lists'; +import { changeValue, submit, reset } from '../../../actions/group_editor'; import IconButton from '../../../components/icon_button'; import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ - label: { id: 'groups.new.title_placeholder', defaultMessage: 'New group title' }, - title: { id: 'groups.new.create', defaultMessage: 'Add group' }, + heading: { id: 'groups.create.heading', defaultMessage: 'New group' }, + title: { id: 'groups.form.title', defaultMessage: 'Title' }, + description: { id: 'groups.form.description', defaultMessage: 'Description' }, + coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Cover Image' }, + create: { id: 'groups.form.create', defaultMessage: 'Create group' }, }); const mapStateToProps = state => ({ - value: state.getIn(['groupEditor', 'title']), - disabled: state.getIn(['groupEditor', 'isSubmitting']), + title: state.getIn(['group_editor', 'title']), + description: state.getIn(['group_editor', 'description']), + coverImage: state.getIn(['group_editor', 'coverImage']), + disabled: state.getIn(['group_editor', 'isSubmitting']), }); const mapDispatchToProps = dispatch => ({ - onChange: value => dispatch(changeListEditorTitle(value)), - onSubmit: () => dispatch(submitListEditor(true)), + onTitleChange: value => dispatch(changeValue('title', value)), + onDescriptionChange: value => dispatch(changeValue('description', value)), + onCoverImageChange: value => dispatch(changeValue('coverImage', value)), + onSubmit: routerHistory => dispatch(submit(routerHistory)), + reset: () => dispatch(reset()), }); export default @connect(mapStateToProps, mapDispatchToProps) @injectIntl class Create extends React.PureComponent { - static propTypes = { - value: PropTypes.string.isRequired, - disabled: PropTypes.bool, - intl: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - }; + static contextTypes = { + router: PropTypes.object + } - handleChange = e => { - this.props.onChange(e.target.value); - } + static propTypes = { + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + coverImage: PropTypes.object, + disabled: PropTypes.bool, + intl: PropTypes.object.isRequired, + onTitleChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + }; - handleSubmit = e => { - e.preventDefault(); - this.props.onSubmit(); - } + componentWillMount() { + this.props.reset(); + } - handleClick = () => { - this.props.onSubmit(); - } + handleTitleChange = e => { + this.props.onTitleChange(e.target.value); + } - render () { - const { value, disabled, intl } = this.props; + handleDescriptionChange = e => { + this.props.onDescriptionChange(e.target.value); + } - const label = intl.formatMessage(messages.label); - const title = intl.formatMessage(messages.title); + handleCoverImageChange = e => { + this.props.onCoverImageChange(e.target.files[0]); + } - return ( -
- + handleClick = () => { + this.props.onSubmit(this.context.router.history); + } - - - ); - } + render () { + const { title, description, coverImage, disabled, intl } = this.props; + + return ( +
+

{intl.formatMessage(messages.heading)}

+ + + + + + + + + + ); + } } diff --git a/app/javascript/gabsocial/features/groups/index/index.js b/app/javascript/gabsocial/features/groups/index/index.js index a582d3ae..c375b892 100644 --- a/app/javascript/gabsocial/features/groups/index/index.js +++ b/app/javascript/gabsocial/features/groups/index/index.js @@ -11,9 +11,10 @@ import GroupCard from './card'; const messages = defineMessages({ heading: { id: 'column.groups', defaultMessage: 'Groups' }, - tab_featured: { id: 'column.groups_tab_featured', defaultMessage: 'Featured' }, - tab_member: { id: 'column.groups_tab_member', defaultMessage: 'Groups you\'re in' }, - tab_admin: { id: 'column.groups_tab_admin', defaultMessage: 'Groups you manage' }, + create: { id: 'groups.create', defaultMessage: 'Create group' }, + tab_featured: { id: 'groups.tab_featured', defaultMessage: 'Featured' }, + tab_member: { id: 'groups.tab_member', defaultMessage: 'Groups you\'re in' }, + tab_admin: { id: 'groups.tab_admin', defaultMessage: 'Groups you manage' }, }); const mapStateToProps = (state, { activeTab }) => ({ @@ -49,6 +50,7 @@ class Groups extends ImmutablePureComponent {
{intl.formatMessage(messages.heading)}
+
{intl.formatMessage(messages.create)}

diff --git a/app/javascript/gabsocial/features/ui/index.js b/app/javascript/gabsocial/features/ui/index.js index afb0d5d0..3c47de7f 100644 --- a/app/javascript/gabsocial/features/ui/index.js +++ b/app/javascript/gabsocial/features/ui/index.js @@ -57,6 +57,7 @@ import { GroupTimeline, GroupMembers, GroupRemovedAccounts, + GroupCreate, } from './util/async-components'; import { me, meUsername } from '../../initial_state'; import { previewState as previewMediaState } from './components/media_modal'; @@ -174,6 +175,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 fbc0cc07..1aa0af28 100644 --- a/app/javascript/gabsocial/features/ui/util/async-components.js +++ b/app/javascript/gabsocial/features/ui/util/async-components.js @@ -42,6 +42,10 @@ export function GroupRemovedAccounts () { return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/removed_accounts'); } +export function GroupCreate () { + return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/create'); +} + export function Groups () { return import(/* webpackChunkName: "features/groups/index" */'../../groups/index'); } diff --git a/app/javascript/gabsocial/reducers/group_editor.js b/app/javascript/gabsocial/reducers/group_editor.js new file mode 100644 index 00000000..054eedd8 --- /dev/null +++ b/app/javascript/gabsocial/reducers/group_editor.js @@ -0,0 +1,56 @@ +import { Map as ImmutableMap } from 'immutable'; +import { + GROUP_CREATE_REQUEST, + GROUP_CREATE_FAIL, + GROUP_CREATE_SUCCESS, + GROUP_UPDATE_REQUEST, + GROUP_UPDATE_FAIL, + GROUP_UPDATE_SUCCESS, + GROUP_EDITOR_RESET, + GROUP_EDITOR_SETUP, + GROUP_EDITOR_VALUE_CHANGE, +} from '../actions/group_editor'; + +const initialState = ImmutableMap({ + groupId: null, + isSubmitting: false, + isChanged: false, + title: '', + description: '', + coverImage: null, +}); + +export default function groupEditorReducer(state = initialState, action) { + switch(action.type) { + case GROUP_EDITOR_RESET: + return initialState; + case GROUP_EDITOR_SETUP: + return state.withMutations(map => { + map.set('groupId', action.group.get('id')); + map.set('title', action.group.get('title')); + map.set('isSubmitting', false); + }); + case GROUP_EDITOR_VALUE_CHANGE: + return state.withMutations(map => { + map.set(action.field, action.value); + map.set('isChanged', true); + }); + case GROUP_CREATE_REQUEST: + case GROUP_UPDATE_REQUEST: + return state.withMutations(map => { + map.set('isSubmitting', true); + map.set('isChanged', false); + }); + case GROUP_CREATE_FAIL: + case GROUP_UPDATE_FAIL: + return state.set('isSubmitting', false); + case GROUP_CREATE_SUCCESS: + case GROUP_UPDATE_SUCCESS: + return state.withMutations(map => { + map.set('isSubmitting', false); + map.set('groupId', action.group.id); + }); + default: + return state; + } +}; diff --git a/app/javascript/gabsocial/reducers/index.js b/app/javascript/gabsocial/reducers/index.js index 144f9743..06370d27 100644 --- a/app/javascript/gabsocial/reducers/index.js +++ b/app/javascript/gabsocial/reducers/index.js @@ -35,6 +35,7 @@ import trends from './trends'; import groups from './groups'; import group_relationships from './group_relationships'; import group_lists from './group_lists'; +import group_editor from './group_editor'; const reducers = { dropdown_menu, @@ -73,6 +74,7 @@ const reducers = { groups, group_relationships, group_lists, + group_editor, }; export default combineReducers(reducers);