diff options
Diffstat (limited to 'web/react/components')
-rw-r--r-- | web/react/components/change_url_modal.jsx | 177 | ||||
-rw-r--r-- | web/react/components/file_upload.jsx | 2 | ||||
-rw-r--r-- | web/react/components/new_channel.jsx | 211 | ||||
-rw-r--r-- | web/react/components/new_channel_flow.jsx | 206 | ||||
-rw-r--r-- | web/react/components/new_channel_modal.jsx | 198 | ||||
-rw-r--r-- | web/react/components/sidebar.jsx | 24 |
6 files changed, 598 insertions, 220 deletions
diff --git a/web/react/components/change_url_modal.jsx b/web/react/components/change_url_modal.jsx new file mode 100644 index 000000000..28fa70c1f --- /dev/null +++ b/web/react/components/change_url_modal.jsx @@ -0,0 +1,177 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Modal = ReactBootstrap.Modal; +var Utils = require('../utils/utils.jsx'); + +export default class ChangeUrlModal extends React.Component { + constructor(props) { + super(props); + + this.onURLChanged = this.onURLChanged.bind(this); + this.doSubmit = this.doSubmit.bind(this); + this.doCancel = this.doCancel.bind(this); + + this.state = { + currentURL: props.currentURL, + urlError: '', + userEdit: false + }; + } + componentWillReceiveProps(nextProps) { + // This check prevents the url being deleted when we re-render + // because of user status check + if (!this.state.userEdit) { + this.setState({ + currentURL: nextProps.currentURL + }); + } + } + componentDidUpdate(prevProps) { + if (this.props.show === true && prevProps.show === false) { + React.findDOMNode(this.refs.urlinput).select(); + } + } + onURLChanged(e) { + const url = e.target.value.trim(); + this.setState({currentURL: url.replace(/[^A-Za-z0-9-_]/g, '').toLowerCase(), userEdit: true}); + } + getURLError(url) { + let error = []; //eslint-disable-line prefer-const + if (url.length < 2) { + error.push(<span key='error1'>{'Must be longer than two characters'}<br/></span>); + } + if (url.charAt(0) === '-' || url.charAt(0) === '_') { + error.push(<span key='error2'>{'Must start with a letter or number'}<br/></span>); + } + if (url.length > 1 && (url.charAt(url.length - 1) === '-' || url.charAt(url.length - 1) === '_')) { + error.push(<span key='error3'>{'Must end with a letter or number'}<br/></span>); + } + if (url.indexOf('__') > -1) { + error.push(<span key='error4'>{'Can not contain two underscores in a row.'}<br/></span>); + } + + // In case of error we don't detect + if (error.length === 0) { + error.push(<span key='errorlast'>{'Invalid URL'}<br/></span>); + } + return error; + } + doSubmit(e) { + e.preventDefault(); + + const url = React.findDOMNode(this.refs.urlinput).value; + const cleanedURL = Utils.cleanUpUrlable(url); + if (cleanedURL !== url || url.length < 2 || url.indexOf('__') > -1) { + this.setState({urlError: this.getURLError(url)}); + return; + } + this.setState({urlError: '', userEdit: false}); + this.props.onModalSubmit(url); + } + doCancel() { + this.setState({urlError: '', userEdit: false}); + this.props.onModalDismissed(); + } + render() { + let urlClass = 'input-group input-group--limit'; + let urlError = null; + let serverError = null; + + if (this.state.urlError) { + urlClass += ' has-error'; + urlError = (<p className='input__help error'>{this.state.urlError}</p>); + } + + if (this.props.serverError) { + serverError = <div className='form-group has-error'><p className='input__help error'>{this.props.serverError}</p></div>; + } + + const fullTeamUrl = Utils.getTeamURLFromAddressBar(); + const teamURL = Utils.getShortenedTeamURL(); + + return ( + <Modal + show={this.props.show} + onHide={this.doCancel} + > + <Modal.Header closeButton={true}> + <Modal.Title>{this.props.title}</Modal.Title> + </Modal.Header> + <form + role='form' + className='form-horizontal' + > + <Modal.Body> + <div className='modal-intro'>{this.props.description}</div> + <div className='form-group'> + <label className='col-sm-2 form__label control-label'>{this.props.urlLabel}</label> + <div className='col-sm-10'> + <div className={urlClass}> + <span + data-toggle='tooltip' + title={fullTeamUrl} + className='input-group-addon' + > + {teamURL} + </span> + <input + type='text' + ref='urlinput' + className='form-control' + maxLength='22' + onChange={this.onURLChanged} + value={this.state.currentURL} + autoFocus={true} + tabIndex='1' + /> + </div> + {urlError} + {serverError} + </div> + </div> + </Modal.Body> + <Modal.Footer> + <button + type='button' + className='btn btn-default' + onClick={this.doCancel} + > + {'Close'} + </button> + <button + onClick={this.doSubmit} + type='submit' + className='btn btn-primary' + tabIndex='2' + > + {this.props.submitButtonText} + </button> + </Modal.Footer> + </form> + </Modal> + ); + } +} + +ChangeUrlModal.defaultProps = { + show: false, + title: 'Change URL', + desciption: '', + urlLabel: 'URL', + submitButtonText: 'Submit', + currentURL: '', + serverError: '' +}; + +ChangeUrlModal.propTypes = { + show: React.PropTypes.bool.isRequired, + title: React.PropTypes.string, + description: React.PropTypes.string, + urlLabel: React.PropTypes.string, + submitButtonText: React.PropTypes.string, + currentURL: React.PropTypes.string, + serverError: React.PropTypes.string, + onModalSubmit: React.PropTypes.func.isRequired, + onModalDismissed: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx index bd8945878..3cb284171 100644 --- a/web/react/components/file_upload.jsx +++ b/web/react/components/file_upload.jsx @@ -52,7 +52,7 @@ export default class FileUpload extends React.Component { } // generate a unique id that can be used by other components to refer back to this upload - var clientId = utils.generateId(); + let clientId = utils.generateId(); // prepare data to be uploaded var formData = new FormData(); diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx deleted file mode 100644 index 1a11fc652..000000000 --- a/web/react/components/new_channel.jsx +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. -// See License.txt for license information. - -var utils = require('../utils/utils.jsx'); -var client = require('../utils/client.jsx'); -var asyncClient = require('../utils/async_client.jsx'); -var UserStore = require('../stores/user_store.jsx'); - -export default class NewChannelModal extends React.Component { - constructor() { - super(); - - this.handleSubmit = this.handleSubmit.bind(this); - this.displayNameKeyUp = this.displayNameKeyUp.bind(this); - this.handleClose = this.handleClose.bind(this); - - this.state = {channelType: ''}; - } - handleSubmit(e) { - e.preventDefault(); - - var channel = {}; - var state = {serverError: ''}; - - channel.display_name = React.findDOMNode(this.refs.display_name).value.trim(); - if (!channel.display_name) { - state.displayNameError = 'This field is required'; - state.inValid = true; - } else if (channel.display_name.length > 22) { - state.displayNameError = 'This field must be less than 22 characters'; - state.inValid = true; - } else { - state.displayNameError = ''; - } - - channel.name = React.findDOMNode(this.refs.channel_name).value.trim(); - if (!channel.name) { - state.nameError = 'This field is required'; - state.inValid = true; - } else if (channel.name.length > 22) { - state.nameError = 'This field must be less than 22 characters'; - state.inValid = true; - } else { - var cleanedName = utils.cleanUpUrlable(channel.name); - if (cleanedName !== channel.name) { - state.nameError = "Must be lowercase alphanumeric characters, allowing '-' but not starting or ending with '-'"; - state.inValid = true; - } else { - state.nameError = ''; - } - } - - this.setState(state); - - if (state.inValid) { - return; - } - - var cu = UserStore.getCurrentUser(); - channel.team_id = cu.team_id; - - channel.description = React.findDOMNode(this.refs.channel_desc).value.trim(); - channel.type = this.state.channelType; - - client.createChannel(channel, - function success(data) { - $(React.findDOMNode(this.refs.modal)).modal('hide'); - - asyncClient.getChannel(data.id); - utils.switchChannel(data); - - React.findDOMNode(this.refs.display_name).value = ''; - React.findDOMNode(this.refs.channel_name).value = ''; - React.findDOMNode(this.refs.channel_desc).value = ''; - }.bind(this), - function error(err) { - state.serverError = err.message; - state.inValid = true; - this.setState(state); - }.bind(this) - ); - } - displayNameKeyUp() { - var displayName = React.findDOMNode(this.refs.display_name).value.trim(); - var channelName = utils.cleanUpUrlable(displayName); - React.findDOMNode(this.refs.channel_name).value = channelName; - } - componentDidMount() { - var self = this; - $(React.findDOMNode(this.refs.modal)).on('show.bs.modal', function onModalShow(e) { - var button = e.relatedTarget; - self.setState({channelType: $(button).attr('data-channeltype')}); - }); - $(React.findDOMNode(this.refs.modal)).on('hidden.bs.modal', this.handleClose); - } - componentWillUnmount() { - $(React.findDOMNode(this.refs.modal)).off('hidden.bs.modal', this.handleClose); - } - handleClose() { - $(React.findDOMNode(this)).find('.form-control').each(function clearForms() { - this.value = ''; - }); - - this.setState({channelType: '', displayNameError: '', nameError: '', serverError: '', inValid: false}); - } - render() { - var displayNameError = null; - var nameError = null; - var serverError = null; - var displayNameClass = 'form-group'; - var nameClass = 'form-group'; - - if (this.state.displayNameError) { - displayNameError = <label className='control-label'>{this.state.displayNameError}</label>; - displayNameClass += ' has-error'; - } - if (this.state.nameError) { - nameError = <label className='control-label'>{this.state.nameError}</label>; - nameClass += ' has-error'; - } - if (this.state.serverError) { - serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>; - } - - var channelTerm = 'Channel'; - if (this.state.channelType === 'P') { - channelTerm = 'Group'; - } - - return ( - <div - className='modal fade' - id='new_channel' - ref='modal' - tabIndex='-1' - role='dialog' - aria-hidden='true' - > - <div className='modal-dialog'> - <div className='modal-content'> - <div className='modal-header'> - <button - type='button' - className='close' - data-dismiss='modal' - > - <span aria-hidden='true'>×</span> - <span className='sr-only'>Cancel</span> - </button> - <h4 className='modal-title'>New {channelTerm}</h4> - </div> - <form role='form'> - <div className='modal-body'> - <div className={displayNameClass}> - <label className='control-label'>Display Name</label> - <input - onKeyUp={this.displayNameKeyUp} - type='text' - ref='display_name' - className='form-control' - placeholder='Enter display name' - maxLength='22' - /> - {displayNameError} - </div> - <div className={nameClass}> - <label className='control-label'>Handle</label> - <input - type='text' - className='form-control' - ref='channel_name' - placeholder="lowercase alphanumeric's only" - maxLength='22' - /> - {nameError} - </div> - <div className='form-group'> - <label className='control-label'>Description</label> - <textarea - className='form-control no-resize' - ref='channel_desc' - rows='3' - placeholder='Description' - maxLength='1024' - /> - </div> - {serverError} - </div> - <div className='modal-footer'> - <button - type='button' - className='btn btn-default' - data-dismiss='modal' - > - Cancel - </button> - <button - onClick={this.handleSubmit} - type='submit' - className='btn btn-primary' - > - Create New {channelTerm} - </button> - </div> - </form> - </div> - </div> - </div> - ); - } -} diff --git a/web/react/components/new_channel_flow.jsx b/web/react/components/new_channel_flow.jsx new file mode 100644 index 000000000..df6a119d5 --- /dev/null +++ b/web/react/components/new_channel_flow.jsx @@ -0,0 +1,206 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Utils = require('../utils/utils.jsx'); +var AsyncClient = require('../utils/async_client.jsx'); +var Client = require('../utils/client.jsx'); +var UserStore = require('../stores/user_store.jsx'); + +var NewChannelModal = require('./new_channel_modal.jsx'); +var ChangeURLModal = require('./change_url_modal.jsx'); + +const SHOW_NEW_CHANNEL = 1; +const SHOW_EDIT_URL = 2; +const SHOW_EDIT_URL_THEN_COMPLETE = 3; + +export default class NewChannelFlow extends React.Component { + constructor(props) { + super(props); + + this.doSubmit = this.doSubmit.bind(this); + this.typeSwitched = this.typeSwitched.bind(this); + this.urlChangeRequested = this.urlChangeRequested.bind(this); + this.urlChangeSubmitted = this.urlChangeSubmitted.bind(this); + this.urlChangeDismissed = this.urlChangeDismissed.bind(this); + this.channelDataChanged = this.channelDataChanged.bind(this); + + this.state = { + serverError: '', + channelType: 'O', + flowState: SHOW_NEW_CHANNEL, + channelDisplayName: '', + channelName: '', + channelDescription: '', + nameModified: false + }; + } + componentWillReceiveProps(nextProps) { + // If we are being shown, grab channel type from props and clear + if (nextProps.show === true && this.props.show === false) { + this.setState({ + serverError: '', + channelType: nextProps.channelType, + flowState: SHOW_NEW_CHANNEL, + channelDisplayName: '', + channelName: '', + channelDescription: '', + nameModified: false + }); + } + } + doSubmit() { + var channel = {}; + + channel.display_name = this.state.channelDisplayName; + if (!channel.display_name) { + this.setState({serverError: 'Invalid Channel Name'}); + return; + } + + channel.name = this.state.channelName; + if (channel.name.length < 2) { + this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE}); + return; + } + + const cu = UserStore.getCurrentUser(); + channel.team_id = cu.team_id; + channel.description = this.state.channelDescription; + channel.type = this.state.channelType; + + Client.createChannel(channel, + (data) => { + this.props.onModalDismissed(); + AsyncClient.getChannel(data.id); + Utils.switchChannel(data); + }, + (err) => { + if (err.message === 'Name must be 2 or more lowercase alphanumeric characters') { + this.setState({flowState: SHOW_EDIT_URL_THEN_COMPLETE}); + } + if (err.message === 'A channel with that handle already exists') { + this.setState({serverError: 'A channel with that URL already exists'}); + return; + } + this.setState({serverError: err.message}); + } + ); + } + typeSwitched() { + if (this.state.channelType === 'P') { + this.setState({channelType: 'O'}); + } else { + this.setState({channelType: 'P'}); + } + } + urlChangeRequested() { + this.setState({flowState: SHOW_EDIT_URL}); + } + urlChangeSubmitted(newURL) { + if (this.state.flowState === SHOW_EDIT_URL_THEN_COMPLETE) { + this.setState({channelName: newURL, nameModified: true}, this.doSubmit); + } else { + this.setState({flowState: SHOW_NEW_CHANNEL, serverError: '', channelName: newURL, nameModified: true}); + } + } + urlChangeDismissed() { + this.setState({flowState: SHOW_NEW_CHANNEL}); + } + channelDataChanged(data) { + this.setState({ + channelDisplayName: data.displayName, + channelDescription: data.description + }); + if (!this.state.nameModified) { + this.setState({channelName: Utils.cleanUpUrlable(data.displayName.trim())}); + } + } + render() { + const channelData = { + name: this.state.channelName, + displayName: this.state.channelDisplayName, + description: this.state.channelDescription + }; + + let showChannelModal = false; + let showGroupModal = false; + let showChangeURLModal = false; + + let changeURLTitle = ''; + let changeURLSubmitButtonText = ''; + let channelTerm = ''; + + // Only listen to flow state if we are being shown + if (this.props.show) { + switch (this.state.flowState) { + case SHOW_NEW_CHANNEL: + if (this.state.channelType === 'O') { + showChannelModal = true; + channelTerm = 'Channel'; + } else { + showGroupModal = true; + channelTerm = 'Group'; + } + break; + case SHOW_EDIT_URL: + showChangeURLModal = true; + changeURLTitle = 'Change ' + channelTerm + ' URL'; + changeURLSubmitButtonText = 'Change ' + channelTerm + ' URL'; + break; + case SHOW_EDIT_URL_THEN_COMPLETE: + showChangeURLModal = true; + changeURLTitle = 'Set ' + channelTerm + ' URL'; + changeURLSubmitButtonText = 'Create ' + channelTerm; + break; + } + } + return ( + <span> + <NewChannelModal + show={showChannelModal} + channelType={'O'} + channelData={channelData} + serverError={this.state.serverError} + onSubmitChannel={this.doSubmit} + onModalDismissed={this.props.onModalDismissed} + onTypeSwitched={this.typeSwitched} + onChangeURLPressed={this.urlChangeRequested} + onDataChanged={this.channelDataChanged} + /> + <NewChannelModal + show={showGroupModal} + channelType={'P'} + channelData={channelData} + serverError={this.state.serverError} + onSubmitChannel={this.doSubmit} + onModalDismissed={this.props.onModalDismissed} + onTypeSwitched={this.typeSwitched} + onChangeURLPressed={this.urlChangeRequested} + onDataChanged={this.channelDataChanged} + /> + <ChangeURLModal + show={showChangeURLModal} + title={changeURLTitle} + description={'Some characters are not allowed in URLs and may be removed.'} + urlLabel={channelTerm + ' URL'} + submitButtonText={changeURLSubmitButtonText} + currentURL={this.state.channelName} + serverError={this.state.serverError} + onModalSubmit={this.urlChangeSubmitted} + onModalDismissed={this.urlChangeDismissed} + /> + </span> + ); + } +} + +NewChannelFlow.defaultProps = { + show: false, + channelType: 'O' +}; + +NewChannelFlow.propTypes = { + show: React.PropTypes.bool.isRequired, + channelType: React.PropTypes.string.isRequired, + onModalDismissed: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/new_channel_modal.jsx b/web/react/components/new_channel_modal.jsx new file mode 100644 index 000000000..2bf645dc5 --- /dev/null +++ b/web/react/components/new_channel_modal.jsx @@ -0,0 +1,198 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +const Utils = require('../utils/utils.jsx'); +var Modal = ReactBootstrap.Modal; + +export default class NewChannelModal extends React.Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + this.handleChange = this.handleChange.bind(this); + + this.state = { + displayNameError: '' + }; + } + componentWillReceiveProps(nextProps) { + if (nextProps.show === true && this.props.show === false) { + this.setState({ + displayNameError: '' + }); + } + } + handleSubmit(e) { + e.preventDefault(); + + const displayName = React.findDOMNode(this.refs.display_name).value.trim(); + if (displayName.length < 1) { + this.setState({displayNameError: 'This field is required'}); + return; + } + + this.props.onSubmitChannel(); + } + handleChange() { + const newData = { + displayName: React.findDOMNode(this.refs.display_name).value, + description: React.findDOMNode(this.refs.channel_desc).value + }; + this.props.onDataChanged(newData); + } + render() { + var displayNameError = null; + var serverError = null; + var displayNameClass = 'form-group'; + + if (this.state.displayNameError) { + displayNameError = <p className='input__help error'>{this.state.displayNameError}</p>; + displayNameClass += ' has-error'; + } + + if (this.props.serverError) { + serverError = <div className='form-group has-error'><p className='input__help error'>{this.props.serverError}</p></div>; + } + + var channelTerm = ''; + var channelSwitchText = ''; + switch (this.props.channelType) { + case 'P': + channelTerm = 'Group'; + channelSwitchText = ( + <div className='modal-intro'> + {'Create a new private group with restricted membership. '} + <a + href='#' + onClick={this.props.onTypeSwitched} + > + {'Create a public channel'} + </a> + </div> + ); + break; + case 'O': + channelTerm = 'Channel'; + channelSwitchText = ( + <div className='modal-intro'> + {'Create a new public channel anyone can join. '} + <a + href='#' + onClick={this.props.onTypeSwitched} + > + {'Create a private group'} + </a> + </div> + ); + break; + } + + const prettyTeamURL = Utils.getShortenedTeamURL(); + + return ( + <span> + <Modal + show={this.props.show} + onHide={this.props.onModalDismissed} + > + <Modal.Header closeButton={true}> + <Modal.Title>{'New ' + channelTerm}</Modal.Title> + </Modal.Header> + <form + role='form' + className='form-horizontal' + > + <Modal.Body> + <div> + {channelSwitchText} + </div> + <div className={displayNameClass}> + <label className='col-sm-2 form__label control-label'>{'Name'}</label> + <div className='col-sm-10'> + <input + onChange={this.handleChange} + type='text' + ref='display_name' + className='form-control' + placeholder='Ex: "Bugs", "Marketing", "办公室恋情"' + maxLength='22' + value={this.props.channelData.displayName} + autoFocus={true} + tabIndex='1' + /> + {displayNameError} + <p className='input__help'> + {'Channel URL: ' + prettyTeamURL + this.props.channelData.name + ' ('} + <a + href='#' + onClick={this.props.onChangeURLPressed} + > + {'change this URL'} + </a> + {')'} + </p> + </div> + </div> + <div className='form-group less'> + <div className='col-sm-2'> + <label className='form__label control-label'>{'Description'}</label> + <label className='form__label light'>{'(optional)'}</label> + </div> + <div className='col-sm-10'> + <textarea + className='form-control no-resize' + ref='channel_desc' + rows='4' + placeholder='Description' + maxLength='1024' + value={this.props.channelData.description} + onChange={this.handleChange} + tabIndex='2' + /> + <p className='input__help'> + {'The purpose of your channel. To help others decide whether to join.'} + </p> + {serverError} + </div> + </div> + </Modal.Body> + <Modal.Footer> + <button + type='button' + className='btn btn-default' + onClick={this.props.onModalDismissed} + > + {'Cancel'} + </button> + <button + onClick={this.handleSubmit} + type='submit' + className='btn btn-primary' + tabIndex='3' + > + {'Create New ' + channelTerm} + </button> + </Modal.Footer> + </form> + </Modal> + </span> + ); + } +} + +NewChannelModal.defaultProps = { + show: false, + channelType: 'O', + serverError: '' +}; +NewChannelModal.propTypes = { + show: React.PropTypes.bool.isRequired, + channelType: React.PropTypes.string.isRequired, + channelData: React.PropTypes.object.isRequired, + serverError: React.PropTypes.string, + onSubmitChannel: React.PropTypes.func.isRequired, + onModalDismissed: React.PropTypes.func.isRequired, + onTypeSwitched: React.PropTypes.func.isRequired, + onChangeURLPressed: React.PropTypes.func.isRequired, + onDataChanged: React.PropTypes.func.isRequired +}; diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index ad934d271..977fecb5c 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -12,6 +12,7 @@ var Utils = require('../utils/utils.jsx'); var SidebarHeader = require('./sidebar_header.jsx'); var SearchBox = require('./search_bar.jsx'); var Constants = require('../utils/constants.jsx'); +var NewChannelFlow = require('./new_channel_flow.jsx'); export default class Sidebar extends React.Component { constructor(props) { @@ -28,6 +29,7 @@ export default class Sidebar extends React.Component { this.createChannelElement = this.createChannelElement.bind(this); this.state = this.getStateFromStores(); + this.state.modal = ''; this.state.loadingDMChannel = -1; } getStateFromStores() { @@ -473,8 +475,18 @@ export default class Sidebar extends React.Component { ); } + let showChannelModal = false; + if (this.state.modal !== '') { + showChannelModal = true; + } + return ( <div> + <NewChannelFlow + show={showChannelModal} + channelType={this.state.modal} + onModalDismissed={() => this.setState({modal: ''})} + /> <SidebarHeader teamDisplayName={this.props.teamDisplayName} teamType={this.props.teamType} @@ -508,11 +520,9 @@ export default class Sidebar extends React.Component { <a className='add-channel-btn' href='#' - data-toggle='modal' - data-target='#new_channel' - data-channeltype='O' + onClick={() => this.setState({modal: 'O'})} > - + + {'+'} </a> </h4> </li> @@ -537,11 +547,9 @@ export default class Sidebar extends React.Component { <a className='add-channel-btn' href='#' - data-toggle='modal' - data-target='#new_channel' - data-channeltype='P' + onClick={() => this.setState({modal: 'P'})} > - + + {'+'} </a> </h4> </li> |