summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/authorize.jsx72
-rw-r--r--web/react/components/popover_list_members.jsx2
-rw-r--r--web/react/components/register_app_modal.jsx249
-rw-r--r--web/react/components/user_settings.jsx10
-rw-r--r--web/react/components/user_settings_developer.jsx93
-rw-r--r--web/react/components/user_settings_modal.jsx11
-rw-r--r--web/react/pages/authorize.jsx21
-rw-r--r--web/react/pages/channel.jsx6
-rw-r--r--web/react/utils/client.jsx33
9 files changed, 492 insertions, 5 deletions
diff --git a/web/react/components/authorize.jsx b/web/react/components/authorize.jsx
new file mode 100644
index 000000000..dd4479ad4
--- /dev/null
+++ b/web/react/components/authorize.jsx
@@ -0,0 +1,72 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Client = require('../utils/client.jsx');
+
+export default class Authorize extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.handleAllow = this.handleAllow.bind(this);
+ this.handleDeny = this.handleDeny.bind(this);
+
+ this.state = {};
+ }
+ handleAllow() {
+ const responseType = this.props.responseType;
+ const clientId = this.props.clientId;
+ const redirectUri = this.props.redirectUri;
+ const state = this.props.state;
+ const scope = this.props.scope;
+
+ Client.allowOAuth2(responseType, clientId, redirectUri, state, scope,
+ (data) => {
+ if (data.redirect) {
+ window.location.replace(data.redirect);
+ }
+ },
+ () => {}
+ );
+ }
+ handleDeny() {
+ window.location.replace(this.props.redirectUri + '?error=access_denied');
+ }
+ render() {
+ return (
+ <div className='authorize-box'>
+ <div className='authorize-inner'>
+ <h3>{'An application would like to connect to your '}{this.props.teamName}{' account'}</h3>
+ <label>{'The app '}{this.props.appName}{' would like the ability to access and modify your basic information.'}</label>
+ <br/>
+ <br/>
+ <label>{'Allow '}{this.props.appName}{' access?'}</label>
+ <br/>
+ <button
+ type='submit'
+ className='btn authorize-btn'
+ onClick={this.handleDeny}
+ >
+ {'Deny'}
+ </button>
+ <button
+ type='submit'
+ className='btn btn-primary authorize-btn'
+ onClick={this.handleAllow}
+ >
+ {'Allow'}
+ </button>
+ </div>
+ </div>
+ );
+ }
+}
+
+Authorize.propTypes = {
+ appName: React.PropTypes.string,
+ teamName: React.PropTypes.string,
+ responseType: React.PropTypes.string,
+ clientId: React.PropTypes.string,
+ redirectUri: React.PropTypes.string,
+ state: React.PropTypes.string,
+ scope: React.PropTypes.string
+};
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index fb9522afb..ec873dd00 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -25,7 +25,7 @@ export default class PopoverListMembers extends React.Component {
$('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true});
$('body').on('click', function onClick(e) {
- if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
+ if (e.target.parentNode && $(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
$('#member_popover').popover('hide');
}
});
diff --git a/web/react/components/register_app_modal.jsx b/web/react/components/register_app_modal.jsx
new file mode 100644
index 000000000..3dd5c094e
--- /dev/null
+++ b/web/react/components/register_app_modal.jsx
@@ -0,0 +1,249 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Client = require('../utils/client.jsx');
+
+export default class RegisterAppModal extends React.Component {
+ constructor() {
+ super();
+
+ this.register = this.register.bind(this);
+ this.onHide = this.onHide.bind(this);
+ this.save = this.save.bind(this);
+
+ this.state = {clientId: '', clientSecret: '', saved: false};
+ }
+ componentDidMount() {
+ $(React.findDOMNode(this)).on('hide.bs.modal', this.onHide);
+ }
+ register() {
+ var state = this.state;
+ state.serverError = null;
+
+ var app = {};
+
+ var name = this.refs.name.getDOMNode().value;
+ if (!name || name.length === 0) {
+ state.nameError = 'Application name must be filled in.';
+ this.setState(state);
+ return;
+ }
+ state.nameError = null;
+ app.name = name;
+
+ var homepage = this.refs.homepage.getDOMNode().value;
+ if (!homepage || homepage.length === 0) {
+ state.homepageError = 'Homepage must be filled in.';
+ this.setState(state);
+ return;
+ }
+ state.homepageError = null;
+ app.homepage = homepage;
+
+ var desc = this.refs.desc.getDOMNode().value;
+ app.description = desc;
+
+ var rawCallbacks = this.refs.callback.getDOMNode().value.trim();
+ if (!rawCallbacks || rawCallbacks.length === 0) {
+ state.callbackError = 'At least one callback URL must be filled in.';
+ this.setState(state);
+ return;
+ }
+ state.callbackError = null;
+ app.callback_urls = rawCallbacks.split('\n');
+
+ Client.registerOAuthApp(app,
+ (data) => {
+ state.clientId = data.id;
+ state.clientSecret = data.client_secret;
+ this.setState(state);
+ },
+ (err) => {
+ state.serverError = err.message;
+ this.setState(state);
+ }
+ );
+ }
+ onHide(e) {
+ if (!this.state.saved && this.state.clientId !== '') {
+ e.preventDefault();
+ return;
+ }
+
+ this.setState({clientId: '', clientSecret: '', saved: false});
+ }
+ save() {
+ this.setState({saved: this.refs.save.getDOMNode().checked});
+ }
+ render() {
+ var nameError;
+ if (this.state.nameError) {
+ nameError = <div className='form-group has-error'><label className='control-label'>{this.state.nameError}</label></div>;
+ }
+ var homepageError;
+ if (this.state.homepageError) {
+ homepageError = <div className='form-group has-error'><label className='control-label'>{this.state.homepageError}</label></div>;
+ }
+ var callbackError;
+ if (this.state.callbackError) {
+ callbackError = <div className='form-group has-error'><label className='control-label'>{this.state.callbackError}</label></div>;
+ }
+ var serverError;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
+ var body = '';
+ if (this.state.clientId === '') {
+ body = (
+ <div className='form-group user-settings'>
+ <h3>{'Register a New Application'}</h3>
+ <br/>
+ <label className='col-sm-4 control-label'>{'Application Name'}</label>
+ <div className='col-sm-7'>
+ <input
+ ref='name'
+ className='form-control'
+ type='text'
+ placeholder='Required'
+ />
+ {nameError}
+ </div>
+ <br/>
+ <br/>
+ <label className='col-sm-4 control-label'>{'Homepage URL'}</label>
+ <div className='col-sm-7'>
+ <input
+ ref='homepage'
+ className='form-control'
+ type='text'
+ placeholder='Required'
+ />
+ {homepageError}
+ </div>
+ <br/>
+ <br/>
+ <label className='col-sm-4 control-label'>{'Description'}</label>
+ <div className='col-sm-7'>
+ <input
+ ref='desc'
+ className='form-control'
+ type='text'
+ placeholder='Optional'
+ />
+ </div>
+ <br/>
+ <br/>
+ <label className='col-sm-4 control-label'>{'Callback URL'}</label>
+ <div className='col-sm-7'>
+ <textarea
+ ref='callback'
+ className='form-control'
+ type='text'
+ placeholder='Required'
+ rows='5'
+ />
+ {callbackError}
+ </div>
+ <br/>
+ <br/>
+ <br/>
+ <br/>
+ <br/>
+ {serverError}
+ <a
+ className='btn btn-sm theme pull-right'
+ href='#'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ {'Cancel'}
+ </a>
+ <a
+ className='btn btn-sm btn-primary pull-right'
+ onClick={this.register}
+ >
+ {'Register'}
+ </a>
+ </div>
+ );
+ } else {
+ var btnClass = ' disabled';
+ if (this.state.saved) {
+ btnClass = '';
+ }
+
+ body = (
+ <div className='form-group user-settings'>
+ <h3>{'Your Application Credentials'}</h3>
+ <br/>
+ <br/>
+ <label className='col-sm-12 control-label'>{'Client ID: '}{this.state.clientId}</label>
+ <label className='col-sm-12 control-label'>{'Client Secret: '}{this.state.clientSecret}</label>
+ <br/>
+ <br/>
+ <br/>
+ <br/>
+ <strong>{'Save these somewhere SAFE and SECURE. We can retrieve your Client Id if you lose it, but your Client Secret will be lost forever if you were to lose it.'}</strong>
+ <br/>
+ <br/>
+ <div className='checkbox'>
+ <label>
+ <input
+ ref='save'
+ type='checkbox'
+ checked={this.state.saved}
+ onClick={this.save}
+ >
+ {'I have saved both my Client Id and Client Secret somewhere safe'}
+ </input>
+ </label>
+ </div>
+ <a
+ className={'btn btn-sm btn-primary pull-right' + btnClass}
+ href='#'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ {'Close'}
+ </a>
+ </div>
+ );
+ }
+
+ return (
+ <div
+ className='modal fade'
+ ref='modal'
+ id='register_app'
+ 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'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>{'x'}</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ {'Developer Applications'}
+ </h4>
+ </div>
+ <div className='modal-body'>
+ {body}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index 2a607b3e0..48b499068 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -7,6 +7,7 @@ var NotificationsTab = require('./user_settings_notifications.jsx');
var SecurityTab = require('./user_settings_security.jsx');
var GeneralTab = require('./user_settings_general.jsx');
var AppearanceTab = require('./user_settings_appearance.jsx');
+var DeveloperTab = require('./user_settings_developer.jsx');
export default class UserSettings extends React.Component {
constructor(props) {
@@ -76,6 +77,15 @@ export default class UserSettings extends React.Component {
/>
</div>
);
+ } else if (this.props.activeTab === 'developer') {
+ return (
+ <div>
+ <DeveloperTab
+ activeSection={this.props.activeSection}
+ updateSection={this.props.updateSection}
+ />
+ </div>
+ );
}
return <div/>;
diff --git a/web/react/components/user_settings_developer.jsx b/web/react/components/user_settings_developer.jsx
new file mode 100644
index 000000000..1b04149dc
--- /dev/null
+++ b/web/react/components/user_settings_developer.jsx
@@ -0,0 +1,93 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var SettingItemMin = require('./setting_item_min.jsx');
+var SettingItemMax = require('./setting_item_max.jsx');
+
+export default class DeveloperTab extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {};
+ }
+ register() {
+ $('#user_settings1').modal('hide');
+ $('#register_app').modal('show');
+ }
+ render() {
+ var appSection;
+ var self = this;
+ if (this.props.activeSection === 'app') {
+ var inputs = [];
+
+ inputs.push(
+ <div className='form-group'>
+ <div className='col-sm-7'>
+ <a
+ className='btn btn-sm btn-primary'
+ onClick={this.register}
+ >
+ {'Register New Application'}
+ </a>
+ </div>
+ </div>
+ );
+
+ appSection = (
+ <SettingItemMax
+ title='Applications (Preview)'
+ inputs={inputs}
+ updateSection={function updateSection(e) {
+ self.props.updateSection('');
+ e.preventDefault();
+ }}
+ />
+ );
+ } else {
+ appSection = (
+ <SettingItemMin
+ title='Applications (Preview)'
+ describe='Open to register a new third-party application'
+ updateSection={function updateSection() {
+ self.props.updateSection('app');
+ }}
+ />
+ );
+ }
+
+ return (
+ <div>
+ <div className='modal-header'>
+ <button
+ type='button'
+ className='close'
+ data-dismiss='modal'
+ aria-label='Close'
+ >
+ <span aria-hidden='true'>{'x'}</span>
+ </button>
+ <h4
+ className='modal-title'
+ ref='title'
+ >
+ <i className='modal-back'></i>{'Developer Settings'}
+ </h4>
+ </div>
+ <div className='user-settings'>
+ <h3 className='tab-header'>{'Developer Settings'}</h3>
+ <div className='divider-dark first'/>
+ {appSection}
+ <div className='divider-dark'/>
+ </div>
+ </div>
+ );
+ }
+}
+
+DeveloperTab.defaultProps = {
+ activeSection: ''
+};
+DeveloperTab.propTypes = {
+ activeSection: React.PropTypes.string,
+ updateSection: React.PropTypes.func
+};
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx
index 7ec75e000..1daf6ebb9 100644
--- a/web/react/components/user_settings_modal.jsx
+++ b/web/react/components/user_settings_modal.jsx
@@ -17,8 +17,8 @@ export default class UserSettingsModal extends React.Component {
$('body').on('click', '.modal-back', function changeDisplay() {
$(this).closest('.modal-dialog').removeClass('display--content');
});
- $('body').on('click', '.modal-header .close', function closeModal() {
- setTimeout(function finishClose() {
+ $('body').on('click', '.modal-header .close', () => {
+ setTimeout(() => {
$('.modal-dialog.display--content').removeClass('display--content');
}, 500);
});
@@ -35,6 +35,9 @@ export default class UserSettingsModal extends React.Component {
tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
+ if (global.window.config.EnableOAuthServiceProvider) {
+ tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'});
+ }
return (
<div
@@ -54,13 +57,13 @@ export default class UserSettingsModal extends React.Component {
data-dismiss='modal'
aria-label='Close'
>
- <span aria-hidden='true'>&times;</span>
+ <span aria-hidden='true'>{'x'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
- Account Settings
+ {'Account Settings'}
</h4>
</div>
<div className='modal-body'>
diff --git a/web/react/pages/authorize.jsx b/web/react/pages/authorize.jsx
new file mode 100644
index 000000000..db42c8266
--- /dev/null
+++ b/web/react/pages/authorize.jsx
@@ -0,0 +1,21 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var Authorize = require('../components/authorize.jsx');
+
+function setupAuthorizePage(teamName, appName, responseType, clientId, redirectUri, scope, state) {
+ React.render(
+ <Authorize
+ teamName={teamName}
+ appName={appName}
+ responseType={responseType}
+ clientId={clientId}
+ redirectUri={redirectUri}
+ scope={scope}
+ state={state}
+ />,
+ document.getElementById('authorize')
+ );
+}
+
+global.window.setup_authorize_page = setupAuthorizePage;
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index fa5710d6b..43493de45 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -33,6 +33,7 @@ var AccessHistoryModal = require('../components/access_history_modal.jsx');
var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
+var RegisterAppModal = require('../components/register_app_modal.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -222,6 +223,11 @@ function setupChannelPage(props) {
/>,
document.getElementById('file_upload_overlay')
);
+
+ React.render(
+ <RegisterAppModal />,
+ document.getElementById('register_app_modal')
+ );
}
global.window.setup_channel_page = setupChannelPage;
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 902eb1642..ba3042d78 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -987,3 +987,36 @@ export function updateValetFeature(data, success, error) {
track('api', 'api_teams_update_valet_feature');
}
+
+export function registerOAuthApp(app, success, error) {
+ $.ajax({
+ url: '/api/v1/oauth/register',
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'POST',
+ data: JSON.stringify(app),
+ success: success,
+ error: (xhr, status, err) => {
+ const e = handleError('registerApp', xhr, status, err);
+ error(e);
+ }
+ });
+
+ module.exports.track('api', 'api_apps_register');
+}
+
+export function allowOAuth2(responseType, clientId, redirectUri, state, scope, success, error) {
+ $.ajax({
+ url: '/api/v1/oauth/allow?response_type=' + responseType + '&client_id=' + clientId + '&redirect_uri=' + redirectUri + '&scope=' + scope + '&state=' + state,
+ dataType: 'json',
+ contentType: 'application/json',
+ type: 'GET',
+ success: success,
+ error: (xhr, status, err) => {
+ const e = handleError('allowOAuth2', xhr, status, err);
+ error(e);
+ }
+ });
+
+ module.exports.track('api', 'api_users_allow_oauth2');
+}