From 0946ddc0b3d4e7113d47778890bdf0bbbaeebcb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Fri, 20 Nov 2015 14:00:10 +0100 Subject: [PATCH 01/13] Minor fix --- js/constants/application_constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 4415a91a..67e79bfb 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -116,7 +116,7 @@ const constants = { 'sdkUrl': 'https://platform.twitter.com/widgets.js' }, 'fartscroll': { - 'sdkUrl': 'http://code.onion.com/fartscroll.js' + 'sdkUrl': 'https://rawgit.com/theonion/fartscroll.js/master/fartscroll.js' } }; From bccfc0906fb4534ccbea3fc8d0358cc53f6acf08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Fri, 20 Nov 2015 16:17:08 +0100 Subject: [PATCH 02/13] Revert super funny-"Important minor fix" We better want this not to go live This reverts commit b77fefb55c1a84b026d5365cd8150d4fb175f7e4. Conflicts: js/constants/application_constants.js --- .../proxy_routes/auth_proxy_handler.js | 20 ------------------- js/constants/application_constants.js | 3 --- 2 files changed, 23 deletions(-) diff --git a/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js b/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js index 18104d7b..b2d552a7 100644 --- a/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js +++ b/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js @@ -8,8 +8,6 @@ import UserActions from '../../../actions/user_actions'; import AppConstants from '../../../constants/application_constants'; -import { InjectInHeadUtils } from '../../../utils/inject_utils'; - const { object } = React.PropTypes; const WHEN_ENUM = ['loggedIn', 'loggedOut']; @@ -55,7 +53,6 @@ export default function AuthProxyHandler({to, when}) { // data from the server if(!UserStore.isLoading()) { this.redirectConditionally(); - this.injectSpecialLoveMessage(); } }, @@ -63,23 +60,6 @@ export default function AuthProxyHandler({to, when}) { UserStore.unlisten(this.onChange); }, - injectSpecialLoveMessage() { - const { currentUser } = this.state; - - if(currentUser && (currentUser.email === 'dimi@mailinator.com' - || currentUser.email === 'trent@ascribe.io' - || currentUser.email === 'wojciech@ascribe.io' - || currentUser.email === 'rod@mailinator.com' - || currentUser.email === 'qisheng.brett.sun@gmail.com' - || currentUser.email === 'sylvain@ascribe.io')) { - if(!InjectInHeadUtils.isPresent('script', AppConstants.fartscroll.sdkUrl)) { - InjectInHeadUtils.inject(AppConstants.fartscroll.sdkUrl).then(() => { - window.fartscroll ? window.fartscroll() : null; - }); - } - } - }, - redirectConditionally() { const { query } = this.props.location; const { redirectAuthenticated, redirect } = query; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 67e79bfb..a58a8cc6 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -114,9 +114,6 @@ const constants = { }, 'twitter': { 'sdkUrl': 'https://platform.twitter.com/widgets.js' - }, - 'fartscroll': { - 'sdkUrl': 'https://rawgit.com/theonion/fartscroll.js/master/fartscroll.js' } }; From 5e45c10baae1e7920586900dd0e1b2681535e54b Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Mon, 23 Nov 2015 10:46:20 +0100 Subject: [PATCH 03/13] Check for piece and edition validity by using their ids instead of titles Also removed a few unnecessary location props. --- js/components/ascribe_detail/edition.js | 6 ++---- js/components/ascribe_detail/edition_container.js | 8 +++----- js/components/ascribe_detail/further_details.js | 6 ++---- js/components/ascribe_detail/piece_container.js | 8 +++----- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/js/components/ascribe_detail/edition.js b/js/components/ascribe_detail/edition.js index 6b38ddf8..254746a6 100644 --- a/js/components/ascribe_detail/edition.js +++ b/js/components/ascribe_detail/edition.js @@ -42,8 +42,7 @@ import { getLangText } from '../../utils/lang_utils'; let Edition = React.createClass({ propTypes: { edition: React.PropTypes.object, - loadEdition: React.PropTypes.func, - location: React.PropTypes.object + loadEdition: React.PropTypes.func }, mixins: [History], @@ -156,8 +155,7 @@ let Edition = React.createClass({ pieceId={this.props.edition.parent} extraData={this.props.edition.extra_data} otherData={this.props.edition.other_data} - handleSuccess={this.props.loadEdition} - location={this.props.location}/> + handleSuccess={this.props.loadEdition} /> diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js index 2c479d24..febe652d 100644 --- a/js/components/ascribe_detail/edition_container.js +++ b/js/components/ascribe_detail/edition_container.js @@ -17,8 +17,7 @@ import { setDocumentTitle } from '../../utils/dom_utils'; */ let EditionContainer = React.createClass({ propTypes: { - params: React.PropTypes.object, - location: React.PropTypes.object + params: React.PropTypes.object }, getInitialState() { @@ -67,14 +66,13 @@ let EditionContainer = React.createClass({ }, render() { - if(this.state.edition && this.state.edition.title) { + if(this.state.edition && this.state.edition.id) { setDocumentTitle([this.state.edition.artist_name, this.state.edition.title].join(', ')); return ( + loadEdition={this.loadEdition} /> ); } else { return ( diff --git a/js/components/ascribe_detail/further_details.js b/js/components/ascribe_detail/further_details.js index 91ce87c5..c178fb93 100644 --- a/js/components/ascribe_detail/further_details.js +++ b/js/components/ascribe_detail/further_details.js @@ -23,8 +23,7 @@ let FurtherDetails = React.createClass({ pieceId: React.PropTypes.number, extraData: React.PropTypes.object, otherData: React.PropTypes.arrayOf(React.PropTypes.object), - handleSuccess: React.PropTypes.func, - location: React.PropTypes.object + handleSuccess: React.PropTypes.func }, getInitialState() { @@ -86,8 +85,7 @@ let FurtherDetails = React.createClass({ overrideForm={true} pieceId={this.props.pieceId} otherData={this.props.otherData} - multiple={true} - location={this.props.location}/> + multiple={true} /> diff --git a/js/components/ascribe_detail/piece_container.js b/js/components/ascribe_detail/piece_container.js index 8cd7df1c..cde6eaea 100644 --- a/js/components/ascribe_detail/piece_container.js +++ b/js/components/ascribe_detail/piece_container.js @@ -50,8 +50,7 @@ import { setDocumentTitle } from '../../utils/dom_utils'; */ let PieceContainer = React.createClass({ propTypes: { - params: React.PropTypes.object, - location: React.PropTypes.object + params: React.PropTypes.object }, mixins: [History], @@ -226,7 +225,7 @@ let PieceContainer = React.createClass({ }, render() { - if(this.state.piece && this.state.piece.title) { + if(this.state.piece && this.state.piece.id) { setDocumentTitle([this.state.piece.artist_name, this.state.piece.title].join(', ')); return ( @@ -292,8 +291,7 @@ let PieceContainer = React.createClass({ pieceId={this.state.piece.id} extraData={this.state.piece.extra_data} otherData={this.state.piece.other_data} - handleSuccess={this.loadPiece} - location={this.props.location}/> + handleSuccess={this.loadPiece} /> From e4cafd4bc3df86e8da992db6bd61c39b120dddaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 23 Nov 2015 19:02:28 +0100 Subject: [PATCH 04/13] Fix bug in FB button component & simplify injectHead util --- .../ascribe_buttons/acl_button_list.js | 2 +- .../facebook_share_button.js | 16 ++++++------- js/utils/inject_utils.js | 23 ++++++++----------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/js/components/ascribe_buttons/acl_button_list.js b/js/components/ascribe_buttons/acl_button_list.js index 42f86320..35e42c20 100644 --- a/js/components/ascribe_buttons/acl_button_list.js +++ b/js/components/ascribe_buttons/acl_button_list.js @@ -41,7 +41,7 @@ let AclButtonList = React.createClass({ componentDidMount() { UserStore.listen(this.onChange); - UserActions.fetchCurrentUser(); + UserActions.fetchCurrentUser.defer(); window.addEventListener('resize', this.handleResize); window.dispatchEvent(new Event('resize')); diff --git a/js/components/ascribe_social_share/facebook_share_button.js b/js/components/ascribe_social_share/facebook_share_button.js index 87a2aef6..aa0b6691 100644 --- a/js/components/ascribe_social_share/facebook_share_button.js +++ b/js/components/ascribe_social_share/facebook_share_button.js @@ -8,7 +8,6 @@ import { InjectInHeadUtils } from '../../utils/inject_utils'; let FacebookShareButton = React.createClass({ propTypes: { - url: React.PropTypes.string, type: React.PropTypes.string }, @@ -28,12 +27,14 @@ let FacebookShareButton = React.createClass({ * To circumvent this, we always have the sdk parse the entire DOM on the initial load * (see FacebookHandler) and then use FB.XFBML.parse() on the mounting component later. */ - if (!InjectInHeadUtils.isPresent('script', AppConstants.facebook.sdkUrl)) { - InjectInHeadUtils.inject(AppConstants.facebook.sdkUrl); - } else { - // Parse() searches the children of the element we give it, not the element itself. - FB.XFBML.parse(this.refs.fbShareButton.getDOMNode().parentElement); - } + + InjectInHeadUtils + .inject(AppConstants.facebook.sdkUrl) + .then(() => { FB.XFBML.parse(this.refs.fbShareButton.getDOMNode().parentElement) }); + }, + + shouldComponentUpdate(nextProps) { + return this.props.type !== nextProps.type; }, render() { @@ -41,7 +42,6 @@ let FacebookShareButton = React.createClass({ ); diff --git a/js/utils/inject_utils.js b/js/utils/inject_utils.js index 174ac8b6..e9430a5e 100644 --- a/js/utils/inject_utils.js +++ b/js/utils/inject_utils.js @@ -12,16 +12,16 @@ let mapTag = { css: 'link' }; +let tags = {}; + function injectTag(tag, src) { - return Q.Promise((resolve, reject) => { - if (isPresent(tag, src)) { - resolve(); - } else { + if(!tags[src]) { + tags[src] = Q.Promise((resolve, reject) => { let attr = mapAttr[tag]; let element = document.createElement(tag); if (tag === 'script') { - element.onload = () => resolve(); - element.onerror = () => reject(); + element.onload = resolve; + element.onerror = reject; } else { resolve(); } @@ -30,14 +30,10 @@ function injectTag(tag, src) { if (tag === 'link') { element.rel = 'stylesheet'; } - } - }); -} + }); + } -function isPresent(tag, src) { - let attr = mapAttr[tag]; - let query = `head > ${tag}[${attr}="${src}"]`; - return document.querySelector(query); + return tags[src]; } function injectStylesheet(src) { @@ -65,7 +61,6 @@ export const InjectInHeadUtils = { * you don't want to embed everything inside the build file. */ - isPresent, injectStylesheet, injectScript, inject From 5423f5dcd7b17bdabcd25f11f39f2974a74e0d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 25 Nov 2015 11:16:09 +0100 Subject: [PATCH 05/13] Bug fix for collapsing nav --- js/components/contract_notification.js | 36 -------------------------- js/components/header.js | 20 +++++++++++--- 2 files changed, 17 insertions(+), 39 deletions(-) delete mode 100644 js/components/contract_notification.js diff --git a/js/components/contract_notification.js b/js/components/contract_notification.js deleted file mode 100644 index cd6ceb53..00000000 --- a/js/components/contract_notification.js +++ /dev/null @@ -1,36 +0,0 @@ -'use strict'; - -import React from 'react'; - -import NotificationStore from '../stores/notification_store'; - -import { mergeOptions } from '../utils/general_utils'; - -let ContractNotification = React.createClass({ - getInitialState() { - return mergeOptions( - NotificationStore.getState() - ); - }, - - componentDidMount() { - NotificationStore.listen(this.onChange); - }, - - componentWillUnmount() { - NotificationStore.unlisten(this.onChange); - }, - - onChange(state) { - this.setState(state); - }, - - render() { - - return ( - null - ); - } -}); - -export default ContractNotification; \ No newline at end of file diff --git a/js/components/header.js b/js/components/header.js index 51f91318..7c2331f9 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -1,9 +1,10 @@ 'use strict'; import React from 'react'; - import { Link } from 'react-router'; +import history from '../history'; + import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; import CollapsibleNav from 'react-bootstrap/lib/CollapsibleNav'; @@ -58,11 +59,17 @@ let Header = React.createClass({ UserStore.listen(this.onChange); WhitelabelActions.fetchWhitelabel(); WhitelabelStore.listen(this.onChange); + + // react-bootstrap 0.25.1 has a bug in which it doesn't + // close the mobile expanded navigation after a click by itself. + // To get rid of this, we set the state of the component ourselves. + history.listen(this.onRouteChange); }, componentWillUnmount() { UserStore.unlisten(this.onChange); WhitelabelStore.unlisten(this.onChange); + history.unlisten(this.onRouteChange); }, getLogo() { @@ -135,6 +142,11 @@ let Header = React.createClass({ this.refs.dropdownbutton.setDropdownState(false); }, + // On route change, close expanded navbar again + onRouteChange() { + this.refs.navbar.state.navExpanded = false; + }, + render() { let account; let signup; @@ -201,8 +213,10 @@ let Header = React.createClass({ - + fixedTop={true} + ref="navbar"> + From 121a1dbb8d5ce7b176e841ce93a738af1b9ae95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 25 Nov 2015 15:00:56 +0100 Subject: [PATCH 06/13] Bump react-router to 1.0.0 because of found bug --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63c6d1e0..83ce69e3 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "react": "0.13.2", "react-bootstrap": "0.25.1", "react-datepicker": "^0.12.0", - "react-router": "^1.0.0-rc3", + "react-router": "1.0.0", "react-router-bootstrap": "^0.19.0", "react-star-rating": "~1.3.2", "react-textarea-autosize": "^2.5.2", From 88d1edd45c78e1e971de3c2e69d3e2cc73a12f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 25 Nov 2015 15:01:23 +0100 Subject: [PATCH 07/13] Add pageExitWarning for SlidesContainer --- .../proxy_routes/auth_proxy_handler.js | 6 ++++-- .../ascribe_slides_container/slides_container.js | 14 ++++++++++---- .../components/ikonotv/ikonotv_register_piece.js | 13 +++++++++---- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js b/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js index b2d552a7..0eb4ad8f 100644 --- a/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js +++ b/js/components/ascribe_routes/proxy_routes/auth_proxy_handler.js @@ -1,7 +1,7 @@ 'use strict'; import React from 'react'; -import { History } from 'react-router'; +import { History, RouteContext } from 'react-router'; import UserStore from '../../../stores/user_store'; import UserActions from '../../../actions/user_actions'; @@ -37,7 +37,9 @@ export default function AuthProxyHandler({to, when}) { location: object }, - mixins: [History], + // We need insert `RouteContext` here in order to be able + // to use the `Lifecycle` widget in further down nested components + mixins: [History, RouteContext], getInitialState() { return UserStore.getState(); diff --git a/js/components/ascribe_slides_container/slides_container.js b/js/components/ascribe_slides_container/slides_container.js index 8ed66c1d..39d515a3 100644 --- a/js/components/ascribe_slides_container/slides_container.js +++ b/js/components/ascribe_slides_container/slides_container.js @@ -1,7 +1,7 @@ 'use strict'; import React from 'react/addons'; -import { History } from 'react-router'; +import { History, Lifecycle } from 'react-router'; import SlidesContainerBreadcrumbs from './slides_container_breadcrumbs'; @@ -17,14 +17,16 @@ const SlidesContainer = React.createClass({ pending: string, complete: string }), - location: object + location: object, + pageExitWarning: string }, - mixins: [History], + mixins: [History, Lifecycle], getInitialState() { return { - containerWidth: 0 + containerWidth: 0, + pageExitWarning: null }; }, @@ -41,6 +43,10 @@ const SlidesContainer = React.createClass({ window.removeEventListener('resize', this.handleContainerResize); }, + routerWillLeave() { + return this.props.pageExitWarning; + }, + handleContainerResize() { this.setState({ // +30 to get rid of the padding of the container which is 15px + 15px left and right diff --git a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js index 98fe6715..d3f93ddf 100644 --- a/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js +++ b/js/components/whitelabel/wallet/components/ikonotv/ikonotv_register_piece.js @@ -32,6 +32,7 @@ import ApiUrls from '../../../../../constants/api_urls'; import { mergeOptions } from '../../../../../utils/general_utils'; import { getLangText } from '../../../../../utils/lang_utils'; + let IkonotvRegisterPiece = React.createClass({ propTypes: { handleSuccess: React.PropTypes.func, @@ -47,7 +48,8 @@ let IkonotvRegisterPiece = React.createClass({ PieceListStore.getState(), PieceStore.getState(), { - step: 0 + step: 0, + pageExitWarning: getLangText("If you leave this form now, your work will not be loaned to Ikono TV.") }); }, @@ -94,7 +96,6 @@ let IkonotvRegisterPiece = React.createClass({ handleRegisterSuccess(response){ - this.refreshPieceList(); // also start loading the piece for the next step @@ -108,7 +109,6 @@ let IkonotvRegisterPiece = React.createClass({ this.incrementStep(); this.refs.slidesContainer.nextSlide(); } - }, handleAdditionalDataSuccess() { @@ -126,6 +126,8 @@ let IkonotvRegisterPiece = React.createClass({ }, handleLoanSuccess(response) { + this.setState({ pageExitWarning: null }); + let notification = new GlobalNotificationModel(response.notification, 'success', 10000); GlobalNotificationActions.appendGlobalNotification(notification); @@ -238,6 +240,8 @@ let IkonotvRegisterPiece = React.createClass({ }, render() { + const { pageExitWarning } = this.state; + return ( + location={this.props.location} + pageExitWarning={pageExitWarning}>
From 3b6481eda0ee994458c69165158ea34771850212 Mon Sep 17 00:00:00 2001 From: Brett Sun Date: Thu, 26 Nov 2015 11:45:40 +0100 Subject: [PATCH 08/13] Bump Rackt/History to 1.13.1 Gracefully handle when sessionStorage is unavailable --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63c6d1e0..30d70815 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "gulp-uglify": "^1.2.0", "gulp-util": "^3.0.4", "harmonize": "^1.4.2", - "history": "^1.11.1", + "history": "^1.13.1", "invariant": "^2.1.1", "isomorphic-fetch": "^2.0.2", "jest-cli": "^0.4.0", From 1c25f8e7765aaba2eb4f663200e3a9dbdb40f1e7 Mon Sep 17 00:00:00 2001 From: diminator Date: Fri, 27 Nov 2015 03:24:37 +0100 Subject: [PATCH 09/13] webhooks in settings without acl --- js/actions/webhook_actions.js | 47 ++++++ .../ascribe_settings/settings_container.js | 2 + .../ascribe_settings/webhook_settings.js | 151 ++++++++++++++++++ js/constants/api_urls.js | 3 + js/fetchers/webhook_fetcher.js | 23 +++ js/stores/webhook_store.js | 27 ++++ 6 files changed, 253 insertions(+) create mode 100644 js/actions/webhook_actions.js create mode 100644 js/components/ascribe_settings/webhook_settings.js create mode 100644 js/fetchers/webhook_fetcher.js create mode 100644 js/stores/webhook_store.js diff --git a/js/actions/webhook_actions.js b/js/actions/webhook_actions.js new file mode 100644 index 00000000..4e5b2462 --- /dev/null +++ b/js/actions/webhook_actions.js @@ -0,0 +1,47 @@ +'use strict'; + +import { alt } from '../alt'; +import WebhookFetcher from '../fetchers/webhook_fetcher'; + + +class WebhookActions { + constructor() { + this.generateActions( + 'updateWebhooks', + 'updateEvents', + 'removeWebhook' + ); + } + + fetchWebhooks() { + WebhookFetcher.fetch() + .then((res) => { + this.actions.updateWebhooks(res.webhooks); + }) + .catch((err) => { + console.logGlobal(err); + }); + } + + fetchWebhookEvents() { + WebhookFetcher.fetchEvents() + .then((res) => { + this.actions.updateEvents(res.events); + }) + .catch((err) => { + console.logGlobal(err); + }); + } + + deleteWebhook(id){ + WebhookFetcher.deleteWebhook(id) + .then((res) => { + this.actions.removeWebhook(id); + }) + .catch((err) => { + console.logGlobal(err); + }); + } +} + +export default alt.createActions(WebhookActions); diff --git a/js/components/ascribe_settings/settings_container.js b/js/components/ascribe_settings/settings_container.js index 5b05e708..35a6fbe5 100644 --- a/js/components/ascribe_settings/settings_container.js +++ b/js/components/ascribe_settings/settings_container.js @@ -11,6 +11,7 @@ import WhitelabelActions from '../../actions/whitelabel_actions'; import AccountSettings from './account_settings'; import BitcoinWalletSettings from './bitcoin_wallet_settings'; import APISettings from './api_settings'; +import WebhookSettings from './webhook_settings'; import AclProxy from '../acl_proxy'; @@ -70,6 +71,7 @@ let SettingsContainer = React.createClass({ aclName="acl_view_settings_api"> + diff --git a/js/components/ascribe_settings/webhook_settings.js b/js/components/ascribe_settings/webhook_settings.js new file mode 100644 index 00000000..1f9eefc5 --- /dev/null +++ b/js/components/ascribe_settings/webhook_settings.js @@ -0,0 +1,151 @@ +'use strict'; + +import React from 'react'; + +import WebhookStore from '../../stores/webhook_store'; +import WebhookActions from '../../actions/webhook_actions'; + +import GlobalNotificationModel from '../../models/global_notification_model'; +import GlobalNotificationActions from '../../actions/global_notification_actions'; + +import Form from '../ascribe_forms/form'; +import Property from '../ascribe_forms/property'; + +import ActionPanel from '../ascribe_panel/action_panel'; +import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph'; + +import ApiUrls from '../../constants/api_urls'; +import AscribeSpinner from '../ascribe_spinner'; + +import { getLangText } from '../../utils/lang_utils'; + + +let WebhookSettings = React.createClass({ + propTypes: { + defaultExpanded: React.PropTypes.bool + }, + + getInitialState() { + return WebhookStore.getState(); + }, + + componentDidMount() { + WebhookStore.listen(this.onChange); + WebhookActions.fetchWebhooks(); + WebhookActions.fetchWebhookEvents(); + }, + + componentWillUnmount() { + WebhookStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + onDeleteWebhook(event) { + let webhookId = event.target.getAttribute('data-id'); + WebhookActions.deleteWebhook(webhookId); + + let notification = new GlobalNotificationModel(getLangText('Webhook deleted'), 'success', 2000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + handleCreateSuccess() { + WebhookActions.fetchWebhooks(); + let notification = new GlobalNotificationModel(getLangText('Webhook successfully created'), 'success', 5000); + GlobalNotificationActions.appendGlobalNotification(notification); + }, + + getWebhooks(){ + let content = ; + + if (this.state.webhooks.length > -1) { + content = this.state.webhooks.map(function(webhook, i) { + const event = webhook.event.split('.')[0]; + return ( + +
+ {event.toUpperCase()} +
+
+ {webhook.target} +
+
+ } + buttons={ +
+
+ +
+
+ }/> + ); + }, this); + } + return content; + }, + + getEvents() { + if (this.state.events && this.state.events.length > 1) { + return ( + + + ); + } + return null; + }, + + + render() { + return ( + +
+ { this.getEvents() } + + + +
+
+
+                    Usage: curl <url> -H 'Authorization: Bearer <token>'
+                
+ {this.getWebhooks()} +
+ ); + } +}); + +export default WebhookSettings; \ No newline at end of file diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index a07f29b1..e7f11141 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -72,6 +72,9 @@ let ApiUrls = { 'users_username': AppConstants.apiEndpoint + 'users/username/', 'users_profile': AppConstants.apiEndpoint + 'users/profile/', 'wallet_settings': AppConstants.apiEndpoint + 'users/wallet_settings/', + 'webhook': AppConstants.apiEndpoint + 'webhooks/${webhook_id}/', + 'webhooks': AppConstants.apiEndpoint + 'webhooks/', + 'webhooks_events': AppConstants.apiEndpoint + 'webhooks/events/', 'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/', 'delete_s3_file': AppConstants.serverUrl + 's3/delete/', 'prize_list': AppConstants.apiEndpoint + 'prize/' diff --git a/js/fetchers/webhook_fetcher.js b/js/fetchers/webhook_fetcher.js new file mode 100644 index 00000000..20775c81 --- /dev/null +++ b/js/fetchers/webhook_fetcher.js @@ -0,0 +1,23 @@ +'use strict'; + +import requests from '../utils/requests'; + +let WebhookFetcher = { + /** + * Fetch the registered webhooks of a user from the API. + */ + fetch() { + return requests.get('webhooks'); + }, + + deleteWebhook(id) { + return requests.delete('webhook', {'webhook_id': id }); + }, + + fetchEvents() { + return requests.get('webhooks_events'); + } + +}; + +export default WebhookFetcher; diff --git a/js/stores/webhook_store.js b/js/stores/webhook_store.js new file mode 100644 index 00000000..95b081a6 --- /dev/null +++ b/js/stores/webhook_store.js @@ -0,0 +1,27 @@ +'use strict'; + +import { alt } from '../alt'; +import WebhookActions from '../actions/webhook_actions'; + + +class WebhookStore { + constructor() { + this.webhooks = {}; + this.events = {}; + this.bindActions(WebhookActions); + } + + onUpdateWebhooks(webhooks) { + this.webhooks = webhooks; + } + + onUpdateEvents(events) { + this.events = events; + } + + onRemoveWebhook(id) { + this.webhooks = this.webhooks.filter((webhook) => webhook.id !== parseInt(id)); + } +} + +export default alt.createStore(WebhookStore, 'WebhookStore'); From 7146f00c0510566a00f8a91f79a84c1e844dcdb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Fri, 27 Nov 2015 13:36:42 +0100 Subject: [PATCH 10/13] Refactor webhooks: add source, clear state on create hide create on no items left --- js/actions/webhook_actions.js | 40 ++-------- .../ascribe_settings/webhook_settings.js | 65 +++++++++-------- .../components/pr_login_container.js | 0 js/fetchers/webhook_fetcher.js | 23 ------ js/sources/webhook_source.js | 46 ++++++++++++ js/stores/webhook_store.js | 73 +++++++++++++++++-- 6 files changed, 153 insertions(+), 94 deletions(-) create mode 100644 js/components/whitelabel/prize/portfolioreview/components/pr_login_container.js delete mode 100644 js/fetchers/webhook_fetcher.js create mode 100644 js/sources/webhook_source.js diff --git a/js/actions/webhook_actions.js b/js/actions/webhook_actions.js index 4e5b2462..f9555ce7 100644 --- a/js/actions/webhook_actions.js +++ b/js/actions/webhook_actions.js @@ -1,47 +1,19 @@ 'use strict'; import { alt } from '../alt'; -import WebhookFetcher from '../fetchers/webhook_fetcher'; class WebhookActions { constructor() { this.generateActions( - 'updateWebhooks', - 'updateEvents', - 'removeWebhook' + 'fetchWebhooks', + 'successFetchWebhooks', + 'fetchWebhookEvents', + 'successFetchWebhookEvents', + 'removeWebhook', + 'successRemoveWebhook' ); } - - fetchWebhooks() { - WebhookFetcher.fetch() - .then((res) => { - this.actions.updateWebhooks(res.webhooks); - }) - .catch((err) => { - console.logGlobal(err); - }); - } - - fetchWebhookEvents() { - WebhookFetcher.fetchEvents() - .then((res) => { - this.actions.updateEvents(res.events); - }) - .catch((err) => { - console.logGlobal(err); - }); - } - - deleteWebhook(id){ - WebhookFetcher.deleteWebhook(id) - .then((res) => { - this.actions.removeWebhook(id); - }) - .catch((err) => { - console.logGlobal(err); - }); - } } export default alt.createActions(WebhookActions); diff --git a/js/components/ascribe_settings/webhook_settings.js b/js/components/ascribe_settings/webhook_settings.js index 1f9eefc5..18c21aa6 100644 --- a/js/components/ascribe_settings/webhook_settings.js +++ b/js/components/ascribe_settings/webhook_settings.js @@ -11,6 +11,8 @@ import GlobalNotificationActions from '../../actions/global_notification_actions import Form from '../ascribe_forms/form'; import Property from '../ascribe_forms/property'; +import AclProxy from '../acl_proxy'; + import ActionPanel from '../ascribe_panel/action_panel'; import CollapsibleParagraph from '../ascribe_collapsible/collapsible_paragraph'; @@ -43,16 +45,18 @@ let WebhookSettings = React.createClass({ this.setState(state); }, - onDeleteWebhook(event) { - let webhookId = event.target.getAttribute('data-id'); - WebhookActions.deleteWebhook(webhookId); + onRemoveWebhook(webhookId) { + return (event) => { + WebhookActions.removeWebhook(webhookId); - let notification = new GlobalNotificationModel(getLangText('Webhook deleted'), 'success', 2000); - GlobalNotificationActions.appendGlobalNotification(notification); + let notification = new GlobalNotificationModel(getLangText('Webhook deleted'), 'success', 2000); + GlobalNotificationActions.appendGlobalNotification(notification); + }; }, handleCreateSuccess() { - WebhookActions.fetchWebhooks(); + this.refs.webhookCreateForm.reset(); + WebhookActions.fetchWebhooks(true); let notification = new GlobalNotificationModel(getLangText('Webhook successfully created'), 'success', 5000); GlobalNotificationActions.appendGlobalNotification(notification); }, @@ -60,7 +64,7 @@ let WebhookSettings = React.createClass({ getWebhooks(){ let content = ; - if (this.state.webhooks.length > -1) { + if (this.state.webhooks) { content = this.state.webhooks.map(function(webhook, i) { const event = webhook.event.split('.')[0]; return ( @@ -82,8 +86,7 @@ let WebhookSettings = React.createClass({
@@ -96,14 +99,13 @@ let WebhookSettings = React.createClass({ }, getEvents() { - if (this.state.events && this.state.events.length > 1) { + if (this.state.webhookEvents && this.state.webhookEvents.length) { return ( - + {this.state.webhookEvents.map((event, i) => { return (