1
0
mirror of https://github.com/ascribe/onion.git synced 2024-12-22 17:33:14 +01:00

Disable 'Collection' header item when no pieces are found for the user

The disabling only happens when the piece list is fetched, which may
not happen immediately in some cases (ie. logged in user goes straight
to another person’s edition first). We’re not able to fetch the piece
list immediately, as some white labels apply default filters on the
piece list which forces them to wait until they can begin fetching
their piece lists.
This commit is contained in:
Brett Sun 2016-01-21 16:47:35 +01:00
parent 9b1e6cc8a1
commit 9559bc09b4
6 changed files with 94 additions and 45 deletions

View File

@ -18,6 +18,8 @@ import AclProxy from './acl_proxy';
import EventActions from '../actions/event_actions'; import EventActions from '../actions/event_actions';
import PieceListStore from '../stores/piece_list_store';
import UserActions from '../actions/user_actions'; import UserActions from '../actions/user_actions';
import UserStore from '../stores/user_store'; import UserStore from '../stores/user_store';
@ -43,12 +45,17 @@ let Header = React.createClass({
getInitialState() { getInitialState() {
return mergeOptions( return mergeOptions(
PieceListStore.getState(),
WhitelabelStore.getState(), WhitelabelStore.getState(),
UserStore.getState() UserStore.getState()
); );
}, },
componentDidMount() { componentDidMount() {
// Listen to the piece list store, but don't fetch immediately to avoid
// conflicts with routes that may need to wait to load the piece list
PieceListStore.listen(this.onChange);
UserStore.listen(this.onChange); UserStore.listen(this.onChange);
UserActions.fetchCurrentUser.defer(); UserActions.fetchCurrentUser.defer();
@ -75,11 +82,16 @@ let Header = React.createClass({
}, },
componentWillUnmount() { componentWillUnmount() {
PieceListStore.unlisten(this.onChange);
UserStore.unlisten(this.onChange); UserStore.unlisten(this.onChange);
WhitelabelStore.unlisten(this.onChange); WhitelabelStore.unlisten(this.onChange);
//history.unlisten(this.onRouteChange); //history.unlisten(this.onRouteChange);
}, },
onChange(state) {
this.setState(state);
},
getLogo() { getLogo() {
let { whitelabel } = this.state; let { whitelabel } = this.state;
@ -117,10 +129,6 @@ let Header = React.createClass({
); );
}, },
onChange(state) {
this.setState(state);
},
onMenuItemClick() { onMenuItemClick() {
/* /*
This is a hack to make the dropdown close after clicking on an item This is a hack to make the dropdown close after clicking on an item
@ -156,16 +164,17 @@ let Header = React.createClass({
}, },
render() { render() {
const { currentUser, unfilteredPieceListCount } = this.state;
let account; let account;
let signup; let signup;
let navRoutesLinks; let navRoutesLinks;
if (this.state.currentUser.username) { if (currentUser.username) {
account = ( account = (
<DropdownButton <DropdownButton
ref='dropdownbutton' ref='dropdownbutton'
eventKey="1" eventKey="1"
title={this.state.currentUser.username}> title={currentUser.username}>
<LinkContainer <LinkContainer
to="/settings" to="/settings"
onClick={this.onMenuItemClick}> onClick={this.onMenuItemClick}>
@ -175,7 +184,7 @@ let Header = React.createClass({
</MenuItem> </MenuItem>
</LinkContainer> </LinkContainer>
<AclProxy <AclProxy
aclObject={this.state.currentUser.acl} aclObject={currentUser.acl}
aclName="acl_view_settings_contract"> aclName="acl_view_settings_contract">
<LinkContainer <LinkContainer
to="/contract_settings" to="/contract_settings"
@ -197,12 +206,18 @@ let Header = React.createClass({
</DropdownButton> </DropdownButton>
); );
// Let's assume that if the piece list hasn't loaded yet (ie. when unfilteredPieceListCount === -1)
// then the user has pieces
// FIXME: this doesn't work that well as the user may not load their piece list
// until much later, so we would show the 'Collection' header as available until
// they actually click on it and get redirected to piece registration.
navRoutesLinks = ( navRoutesLinks = (
<NavRoutesLinks <NavRoutesLinks
navbar navbar
right right
hasPieces={!!unfilteredPieceListCount}
routes={this.props.routes} routes={this.props.routes}
userAcl={this.state.currentUser.acl} /> userAcl={currentUser.acl} />
); );
} else { } else {
account = ( account = (

View File

@ -11,14 +11,33 @@ import AclProxy from './acl_proxy';
import { sanitizeList } from '../utils/general_utils'; import { sanitizeList } from '../utils/general_utils';
const DISABLE_ENUM = ['hasPieces', 'noPieces'];
let NavRoutesLinks = React.createClass({ let NavRoutesLinks = React.createClass({
propTypes: { propTypes: {
hasPieces: React.PropTypes.bool,
routes: React.PropTypes.arrayOf(React.PropTypes.object), routes: React.PropTypes.arrayOf(React.PropTypes.object),
userAcl: React.PropTypes.object userAcl: React.PropTypes.object
}, },
isRouteDisabled(disableOn) {
const { hasPieces } = this.props;
if (disableOn) {
if (!DISABLE_ENUM.includes(disableOn)) {
throw new Error(`"disableOn" must be one of: [${DISABLE_ENUM.join(', ')}] got "${disableOn}" instead`);
}
if (disableOn === 'hasPieces') {
return hasPieces;
} else if (disableOn === 'noPieces') {
return !hasPieces;
}
}
},
/** /**
* This method generales a bunch of react-bootstrap specific links * This method generates a bunch of react-bootstrap specific links
* from the routes we defined in one of the specific routes.js file * from the routes we defined in one of the specific routes.js file
* *
* We can define a headerTitle as well as a aclName and according to that the * We can define a headerTitle as well as a aclName and according to that the
@ -34,20 +53,29 @@ let NavRoutesLinks = React.createClass({
} }
const links = node.childRoutes.map((child, j) => { const links = node.childRoutes.map((child, j) => {
const { aclName, headerTitle, path, childRoutes } = child; const { aclName, disableOn, headerTitle, path, childRoutes } = child;
let childrenFn = null;
// If the node has children that could be rendered, then we want
// to execute this function again with the child as the root
//
// Otherwise we'll just pass childrenFn as false
if (child.childRoutes && child.childRoutes.length) {
childrenFn = this.extractLinksFromRoutes(child, userAcl, i++);
}
// We validate if the user has set the title correctly, // We validate if the user has set the title correctly,
// otherwise we're not going to render his route // otherwise we're not going to render his route
if (headerTitle && typeof headerTitle === 'string') { if (headerTitle && typeof headerTitle === 'string') {
let nestedChildren = null;
// If the node has children that could be rendered, then we want
// to execute this function again with the child as the root
//
// Otherwise we'll just pass nestedChildren as false
if (child.childRoutes && child.childRoutes.length) {
nestedChildren = this.extractLinksFromRoutes(child, userAcl, i++);
}
const navLinkProps = {
headerTitle,
children: nestedChildren,
depth: i,
disabled: this.isRouteDisabled(disableOn),
routePath: `/${path}`
};
// if there is an aclName present on the route definition, // if there is an aclName present on the route definition,
// we evaluate it against the user's acl // we evaluate it against the user's acl
if (aclName && typeof aclName !== 'undefined') { if (aclName && typeof aclName !== 'undefined') {
@ -56,21 +84,14 @@ let NavRoutesLinks = React.createClass({
key={j} key={j}
aclName={aclName} aclName={aclName}
aclObject={this.props.userAcl}> aclObject={this.props.userAcl}>
<NavRoutesLinksLink <NavRoutesLinksLink {...navLinkProps} />
headerTitle={headerTitle}
routePath={'/' + path}
depth={i}
children={childrenFn} />
</AclProxy> </AclProxy>
); );
} else { } else {
return ( return (
<NavRoutesLinksLink <NavRoutesLinksLink
key={j} key={j}
headerTitle={headerTitle} {...navLinkProps} />
routePath={'/' + path}
depth={i}
children={childrenFn} />
); );
} }
} else { } else {

View File

@ -11,25 +11,26 @@ import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
let NavRoutesLinksLink = React.createClass({ let NavRoutesLinksLink = React.createClass({
propTypes: { propTypes: {
headerTitle: React.PropTypes.string,
routePath: React.PropTypes.string,
children: React.PropTypes.oneOfType([ children: React.PropTypes.oneOfType([
React.PropTypes.arrayOf(React.PropTypes.element), React.PropTypes.arrayOf(React.PropTypes.element),
React.PropTypes.element React.PropTypes.element
]), ]),
disabled: React.PropTypes.bool,
depth: React.PropTypes.number depth: React.PropTypes.number,
headerTitle: React.PropTypes.string,
routePath: React.PropTypes.string
}, },
render() { render() {
const { children, headerTitle, depth, routePath } = this.props; const { children, headerTitle, depth, disabled, routePath } = this.props;
// if the route has children, we're returning a DropdownButton that will get filled // if the route has children, we're returning a DropdownButton that will get filled
// with MenuItems // with MenuItems
if (children) { if (children) {
return ( return (
<DropdownButton title={headerTitle}> <DropdownButton
disabled={disabled}
title={headerTitle}>
{children} {children}
</DropdownButton> </DropdownButton>
); );
@ -38,13 +39,17 @@ let NavRoutesLinksLink = React.createClass({
// if the node's child is actually a node of level one (a child of a node), we're // if the node's child is actually a node of level one (a child of a node), we're
// returning a DropdownButton matching MenuItem // returning a DropdownButton matching MenuItem
return ( return (
<LinkContainer to={routePath}> <LinkContainer
disabled={disabled}
to={routePath}>
<MenuItem>{headerTitle}</MenuItem> <MenuItem>{headerTitle}</MenuItem>
</LinkContainer> </LinkContainer>
); );
} else if (depth === 0) { } else if (depth === 0) {
return ( return (
<LinkContainer to={routePath}> <LinkContainer
disabled={disabled}
to={routePath}>
<NavItem>{headerTitle}</NavItem> <NavItem>{headerTitle}</NavItem>
</LinkContainer> </LinkContainer>
); );

View File

@ -54,7 +54,8 @@ const ROUTES = {
<Route <Route
path='collection' path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPPieceList)} component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPPieceList)}
headerTitle='COLLECTION'/> headerTitle='COLLECTION'
disableOn='noPieces' />
<Route path='pieces/:pieceId' component={SluicePieceContainer} /> <Route path='pieces/:pieceId' component={SluicePieceContainer} />
<Route path='editions/:editionId' component={EditionContainer} /> <Route path='editions/:editionId' component={EditionContainer} />
<Route path='verify' component={CoaVerifyContainer} /> <Route path='verify' component={CoaVerifyContainer} />
@ -70,7 +71,8 @@ const ROUTES = {
<Route <Route
path='collection' path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPPieceList)} component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(SPPieceList)}
headerTitle='SUBMISSIONS'/> headerTitle='SUBMISSIONS'
disableOn='noPieces' />
<Route <Route
path='login' path='login'
component={ProxyHandler( component={ProxyHandler(

View File

@ -75,7 +75,8 @@ let ROUTES = {
<Route <Route
path='collection' path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(CylandPieceList)} component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(CylandPieceList)}
headerTitle='COLLECTION' /> headerTitle='COLLECTION'
disableOn='noPieces' />
<Route path='editions/:editionId' component={EditionContainer} /> <Route path='editions/:editionId' component={EditionContainer} />
<Route path='verify' component={CoaVerifyContainer} /> <Route path='verify' component={CoaVerifyContainer} />
<Route path='pieces/:pieceId' component={CylandPieceContainer} /> <Route path='pieces/:pieceId' component={CylandPieceContainer} />
@ -109,7 +110,8 @@ let ROUTES = {
<Route <Route
path='collection' path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(PieceList)} component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(PieceList)}
headerTitle='COLLECTION' /> headerTitle='COLLECTION'
disableOn='noPieces' />
<Route path='pieces/:pieceId' component={PieceContainer} /> <Route path='pieces/:pieceId' component={PieceContainer} />
<Route path='editions/:editionId' component={EditionContainer} /> <Route path='editions/:editionId' component={EditionContainer} />
<Route path='verify' component={CoaVerifyContainer} /> <Route path='verify' component={CoaVerifyContainer} />
@ -150,7 +152,8 @@ let ROUTES = {
<Route <Route
path='collection' path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvPieceList)} component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvPieceList)}
headerTitle='COLLECTION' /> headerTitle='COLLECTION'
disableOn='noPieces' />
<Route <Route
path='contract_notifications' path='contract_notifications'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} /> component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(IkonotvContractNotifications)} />
@ -189,7 +192,8 @@ let ROUTES = {
<Route <Route
path='collection' path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketPieceList)} component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(MarketPieceList)}
headerTitle='COLLECTION' /> headerTitle='COLLECTION'
disableOn='noPieces' />
<Route path='pieces/:pieceId' component={MarketPieceContainer} /> <Route path='pieces/:pieceId' component={MarketPieceContainer} />
<Route path='editions/:editionId' component={MarketEditionContainer} /> <Route path='editions/:editionId' component={MarketEditionContainer} />
<Route path='verify' component={CoaVerifyContainer} /> <Route path='verify' component={CoaVerifyContainer} />
@ -225,7 +229,8 @@ let ROUTES = {
<Route <Route
path='collection' path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(Vivi23PieceList)} component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(Vivi23PieceList)}
headerTitle='COLLECTION' /> headerTitle='COLLECTION'
disableOn='noPieces' />
<Route path='pieces/:pieceId' component={MarketPieceContainer} /> <Route path='pieces/:pieceId' component={MarketPieceContainer} />
<Route path='editions/:editionId' component={MarketEditionContainer} /> <Route path='editions/:editionId' component={MarketEditionContainer} />
<Route path='verify' component={CoaVerifyContainer} /> <Route path='verify' component={CoaVerifyContainer} />

View File

@ -40,7 +40,8 @@ let COMMON_ROUTES = (
<Route <Route
path='collection' path='collection'
component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(PieceList)} component={ProxyHandler(AuthRedirect({to: '/login', when: 'loggedOut'}))(PieceList)}
headerTitle='COLLECTION'/> headerTitle='COLLECTION'
disableOn='noPieces' />
<Route <Route
path='signup' path='signup'
component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} /> component={ProxyHandler(AuthRedirect({to: '/collection', when: 'loggedIn'}))(SignupContainer)} />