diff --git a/js/actions/piece_list_actions.js b/js/actions/piece_list_actions.js index 2fd15c04..ae5ac090 100644 --- a/js/actions/piece_list_actions.js +++ b/js/actions/piece_list_actions.js @@ -57,7 +57,7 @@ class PieceListActions { PieceListFetcher .fetchRequestActions() .then((res) => { - this.actions.updatePieceListRequestActions(res.piece_ids); + this.actions.updatePieceListRequestActions(res); }) .catch((err) => console.logGlobal(err)); } diff --git a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js index f7bca334..178a7db4 100644 --- a/js/components/ascribe_accordion_list/accordion_list_item_wallet.js +++ b/js/components/ascribe_accordion_list/accordion_list_item_wallet.js @@ -61,12 +61,13 @@ let AccordionListItemWallet = React.createClass({ }, getGlyphicon(){ - if (this.props.content.requestAction && this.props.content.requestAction.length > 0) { + if ((this.props.content.request_action && this.props.content.request_action.length > 0) || + (this.props.content.request_action_editions)){ return ( {getLangText('You have actions pending in one of your editions')}}> + overlay={{getLangText('You have actions pending')}}> ); } diff --git a/js/components/ascribe_detail/edition_container.js b/js/components/ascribe_detail/edition_container.js index 3be63974..78b1e477 100644 --- a/js/components/ascribe_detail/edition_container.js +++ b/js/components/ascribe_detail/edition_container.js @@ -36,6 +36,15 @@ let EditionContainer = React.createClass({ EditionActions.fetchOne(this.props.params.editionId); }, + // This is done to update the container when the user clicks on the prev or next + // button to update the URL parameter (and therefore to switch pieces) + componentWillReceiveProps(nextProps) { + if(this.props.params.editionId !== nextProps.params.editionId) { + EditionActions.updateEdition({}); + EditionActions.fetchOne(nextProps.params.editionId); + } + }, + componentWillUnmount() { // Every time we're leaving the edition detail page, // just reset the edition that is saved in the edition store diff --git a/js/components/ascribe_forms/form_create_contract.js b/js/components/ascribe_forms/form_create_contract.js index 1b3fcfd6..416ae0e5 100644 --- a/js/components/ascribe_forms/form_create_contract.js +++ b/js/components/ascribe_forms/form_create_contract.js @@ -18,14 +18,20 @@ let CreateContractForm = React.createClass({ getInitialState() { return { - digitalWorkKey: null, + contractKey: null, isUploadReady: false }; }, + getFormData(){ + return { + blob: this.state.contractKey + }; + }, + submitKey(key) { this.setState({ - digitalWorkKey: key + contractKey: key }); }, @@ -39,6 +45,7 @@ let CreateContractForm = React.createClass({ return (
+ handleSuccess={this.handleSuccess} /> ); } else if(this.props.requestAction === 'loan_request') { return ( @@ -110,7 +118,7 @@ let RequestActionForm = React.createClass({ buttonAcceptClassName='inline pull-right btn-sm ascribe-margin-1px' pieceOrEditions={this.props.pieceOrEditions} currentUser={this.props.currentUser} - handleSuccess={this.props.handleSuccess} /> + handleSuccess={this.handleSuccess} /> ); } else { return ( diff --git a/js/components/global_action.js b/js/components/global_action.js new file mode 100644 index 00000000..80df0c75 --- /dev/null +++ b/js/components/global_action.js @@ -0,0 +1,43 @@ +'use strict'; + +import React from 'react'; + +let GlobalAction = React.createClass({ + propTypes: { + requestActions: React.PropTypes.object + }, + + render() { + let pieceActions = null; + if (this.props.requestActions && this.props.requestActions.pieces){ + pieceActions = this.props.requestActions.pieces.map((item) => { + return ( +
+ {item} +
); + }); + } + let editionActions = null; + if (this.props.requestActions && this.props.requestActions.editions){ + editionActions = Object.keys(this.props.requestActions.editions).map((pieceId) => { + return this.props.requestActions.editions[pieceId].map((item) => { + return ( +
+ {item} +
); + }); + }); + } + + if (pieceActions || editionActions) { + return ( +
+ {pieceActions} + {editionActions} +
); + } + return null; + } +}); + +export default GlobalAction; \ No newline at end of file diff --git a/js/components/header.js b/js/components/header.js index 0863624f..dcf3c475 100644 --- a/js/components/header.js +++ b/js/components/header.js @@ -3,12 +3,6 @@ import React from 'react'; import Router from 'react-router'; -import UserActions from '../actions/user_actions'; -import UserStore from '../stores/user_store'; - -import WhitelabelActions from '../actions/whitelabel_actions'; -import WhitelabelStore from '../stores/whitelabel_store'; -import EventActions from '../actions/event_actions'; import Nav from 'react-bootstrap/lib/Nav'; import Navbar from 'react-bootstrap/lib/Navbar'; @@ -18,6 +12,15 @@ import MenuItem from 'react-bootstrap/lib/MenuItem'; import MenuItemLink from 'react-router-bootstrap/lib/MenuItemLink'; import NavItemLink from 'react-router-bootstrap/lib/NavItemLink'; +import UserActions from '../actions/user_actions'; +import UserStore from '../stores/user_store'; + +import WhitelabelActions from '../actions/whitelabel_actions'; +import WhitelabelStore from '../stores/whitelabel_store'; +import EventActions from '../actions/event_actions'; + +import HeaderNotifications from './header_notification'; + import HeaderNotificationDebug from './header_notification_debug'; import NavRoutesLinks from './nav_routes_links'; @@ -41,7 +44,10 @@ let Header = React.createClass({ }, getInitialState() { - return mergeOptions(WhitelabelStore.getState(), UserStore.getState()); + return mergeOptions( + WhitelabelStore.getState(), + UserStore.getState() + ); }, componentDidMount() { @@ -96,11 +102,13 @@ let Header = React.createClass({ let navRoutesLinks; if (this.state.currentUser.username){ account = ( - + {getLangText('Account Settings')} {getLangText('Log out')} - + ); navRoutesLinks = ; } @@ -126,6 +134,7 @@ let Header = React.createClass({ {account} {signup} + {navRoutesLinks} diff --git a/js/components/header_notification.js b/js/components/header_notification.js new file mode 100644 index 00000000..9222c0c4 --- /dev/null +++ b/js/components/header_notification.js @@ -0,0 +1,144 @@ +'use strict'; + +import React from 'react'; +import Router from 'react-router'; +import DropdownButton from 'react-bootstrap/lib/DropdownButton'; +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; +import MenuItem from 'react-bootstrap/lib/MenuItem'; + +import Nav from 'react-bootstrap/lib/Nav'; + +import PieceListStore from '../stores/piece_list_store'; + +import { mergeOptions } from '../utils/general_utils'; +import { getLangText } from '../utils/lang_utils'; + +let Link = Router.Link; + + +let HeaderNotifications = React.createClass({ + + getInitialState() { + return mergeOptions( + PieceListStore.getState() + ); + }, + + componentDidMount() { + PieceListStore.listen(this.onChange); + }, + + componentWillUnmount() { + PieceListStore.unlisten(this.onChange); + }, + + onChange(state) { + this.setState(state); + }, + + onSelected(event) { + /* + This is a hack to make the dropdown close after clicking on an item + The function just need to be defined + + from https://github.com/react-bootstrap/react-bootstrap/issues/368: + + @jvillasante - Have you tried to use onSelect with the DropdownButton? + I don't have a working example that is exactly like yours, + but I just noticed that the Dropdown closes when I've attached an event handler to OnSelect: + + + + onSelected: function(e) { + // doesn't need to have functionality (necessarily) ... just wired up + } + Internally, a call to DropdownButton.setDropDownState(false) is made which will hide the dropdown menu. + So, you should be able to call that directly on the DropdownButton instance as well if needed. + */ + }, + + render() { + if (this.state.requestActions && this.state.requestActions.length > 0) { + return ( + + ); + } + return null; + } +}); + +let NotificationListItem = React.createClass({ + propTypes: { + pieceOrEdition: React.PropTypes.object + }, + + getLinkData() { + + if(this.props.pieceOrEdition && this.props.pieceOrEdition.parent) { + return { + to: 'edition', + params: { + editionId: this.props.pieceOrEdition.bitcoin_id + } + }; + } else { + return { + to: 'piece', + params: { + pieceId: this.props.pieceOrEdition.id + } + }; + } + + }, + + render() { + if (this.props.pieceOrEdition) { + return ( + +
+
+
+ +
+
+
+

{this.props.pieceOrEdition.title}

+
by {this.props.pieceOrEdition.artist_name}
+
+ { + this.props.pieceOrEdition.request_action.map((requestAction) => { + return 'Pending ' + requestAction.action + ' request'; + }) + } +
+
+
+ ); + } + return null; + } +}); + +export default HeaderNotifications; diff --git a/js/components/piece_list.js b/js/components/piece_list.js index 3ecdd135..d841f7af 100644 --- a/js/components/piece_list.js +++ b/js/components/piece_list.js @@ -15,6 +15,7 @@ import AccordionListItemTableEditions from './ascribe_accordion_list/accordion_l import Pagination from './ascribe_pagination/pagination'; +import GlobalAction from './global_action'; import PieceListBulkModal from './ascribe_piece_list_bulk_modal/piece_list_bulk_modal'; import PieceListToolbar from './ascribe_piece_list_toolbar/piece_list_toolbar'; @@ -148,6 +149,10 @@ let PieceList = React.createClass({ render() { let loadingElement = (); let AccordionListItemType = this.props.accordionListItemType; + + // + + return (
{ - piece.requestAction = requestActions.indexOf(piece.id) > -1; - }); + onUpdatePieceListRequestActions(res) { + this.requestActions = res.actions; } onUpdatePropertyForPiece({pieceId, key, value}) { diff --git a/sass/ascribe_accordion_list.scss b/sass/ascribe_accordion_list.scss index 4b4186eb..6412a598 100644 --- a/sass/ascribe_accordion_list.scss +++ b/sass/ascribe_accordion_list.scss @@ -166,7 +166,7 @@ $ascribe-accordion-list-item-height: 8em; .request-action-badge { color: $ascribe-color-green; font-size: 1.2em; - padding: .3em; + padding: .8em; position: absolute; right: 0; top: 0; diff --git a/sass/ascribe_global_action.scss b/sass/ascribe_global_action.scss new file mode 100644 index 00000000..af4eca4a --- /dev/null +++ b/sass/ascribe_global_action.scss @@ -0,0 +1,25 @@ +$break-small: 764px; +$break-medium: 991px; +$break-medium: 1200px; + +.ascribe-global-action-wrapper { + position: fixed; + width: 100%; + max-width: 500px; + height:3.5em; + left:0; + right: 0; + top:0; + z-index: 2000; + display:table; + margin: 1px auto; +} + +.ascribe-global-action { + text-align: center; + padding: 1em; + color: black; + border: 1px solid #cccccc; + background-color: white; + margin-top: 1px; +} \ No newline at end of file diff --git a/sass/ascribe_notification_list.scss b/sass/ascribe_notification_list.scss new file mode 100644 index 00000000..bd3c0b20 --- /dev/null +++ b/sass/ascribe_notification_list.scss @@ -0,0 +1,65 @@ +$break-small: 764px; +$break-medium: 991px; +$break-medium: 1200px; + +.notification-wrapper { + width: 350px; + height:8em; + padding: 0.3em; + border-bottom: 1px solid #cccccc; + margin: -3px -20px; + + // ToDo: Include media queries for thumbnail + .thumbnail-wrapper { + width: 7.4em; + height: 7.4em; + padding:0; + cursor: pointer; + text-align: center; + img { + max-width: 100%; + max-height: 100%; + } + &::before { + content: ' '; + display: inline-block; + vertical-align: middle; /* vertical alignment of the inline element */ + height: 100%; + } + } + h1 { + margin-top: 0.3em; + margin-bottom: 0.15em; + font-size: 1.8em; + } + .sub-header{ + margin-bottom: 1em; + } + .notification-action{ + color: $ascribe-color-green; + } +} + +.notification-menu { + .dropdown-menu { + padding: 0 !important; + li a { + padding-top: 0; + } + } +} + +.notification-amount { + padding: 0.3em; + font-size: 1.2em; + +} + +.ascribe-global-action { + text-align: center; + padding: 1em; + color: black; + border: 1px solid #cccccc; + background-color: white; + margin-top: 1px; +} \ No newline at end of file diff --git a/sass/main.scss b/sass/main.scss index 6ec1d37d..fb71ff2a 100644 --- a/sass/main.scss +++ b/sass/main.scss @@ -22,7 +22,9 @@ $BASE_URL: '<%= BASE_URL %>'; @import 'ascribe_media_player'; @import 'ascribe_uploader'; @import 'ascribe_footer'; +@import 'ascribe_global_action'; @import 'ascribe_global_notification'; +@import 'ascribe_notification_list'; @import 'ascribe_piece_register'; @import 'offset_right'; @import 'ascribe_settings';