Merge pull request #132 from sharetribe/transaction-pages-fix-links-with-wrong-role

Enforcing role on order & sale pages
This commit is contained in:
Vesa Luusua 2017-04-29 00:22:21 +03:00 committed by GitHub
commit 9e18456d8a
6 changed files with 43 additions and 12 deletions

View file

@ -46,7 +46,7 @@ export const fetchOrder = id =>
dispatch(fetchOrderRequest());
return sdk.transactions
.show({ id, include: ['provider', 'listing', 'booking'] }, { expand: true })
.show({ id, include: ['customer', 'provider', 'listing', 'booking'] }, { expand: true })
.then(response => {
dispatch(addEntities(response));
dispatch(fetchOrderSuccess(response));

View file

@ -2,7 +2,7 @@ import React, { PropTypes } from 'react';
import classNames from 'classnames';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { OrderDetailsPanel, PageLayout } from '../../components';
import { NamedRedirect, OrderDetailsPanel, PageLayout } from '../../components';
import * as propTypes from '../../util/propTypes';
import { ensureBooking, ensureListing, ensureTransaction, ensureUser } from '../../util/data';
import { getEntities } from '../../ducks/sdk.duck';
@ -13,11 +13,20 @@ import css from './OrderPage.css';
// OrderPage handles data loading
// It show loading data text or OrderDetailsPanel (and later also another panel for messages).
export const OrderPageComponent = props => {
const { intl, transaction } = props;
const { currentUser, intl, transaction } = props;
const currentTransaction = ensureTransaction(transaction);
const currentListing = ensureListing(currentTransaction.listing);
const title = currentListing.attributes.title;
// Redirect users with someone else's direct link to their own inbox/orders page.
const isDataAvailable = currentUser && currentTransaction.id && currentTransaction.customer;
const isOwnSale = isDataAvailable && currentUser.id.uuid === currentTransaction.customer.id.uuid;
if (isDataAvailable && !isOwnSale) {
// eslint-disable-next-line no-console
console.error('Tried to access an order that was not owned by the current user');
return <NamedRedirect name="InboxPage" params={{ tab: 'orders' }} />;
}
const detailsProps = {
totalPrice: currentTransaction.attributes.total,
orderState: currentTransaction.attributes.state,
@ -31,7 +40,7 @@ export const OrderPageComponent = props => {
[css.tabContentVisible]: props.tab === 'details',
});
const panel = currentTransaction.id
const panel = isDataAvailable && currentTransaction.id
? <OrderDetailsPanel className={detailsClassName} {...detailsProps} />
: <h1 className={css.title}><FormattedMessage id="OrderPage.loadingData" /></h1>;
@ -42,11 +51,12 @@ export const OrderPageComponent = props => {
);
};
OrderPageComponent.defaultProps = { transaction: null };
OrderPageComponent.defaultProps = { transaction: null, currentUser: null };
const { oneOf } = PropTypes;
OrderPageComponent.propTypes = {
currentUser: propTypes.currentUser,
intl: intlShape.isRequired,
tab: oneOf(['details', 'discussion']).isRequired,
transaction: propTypes.transaction,

View file

@ -1,6 +1,7 @@
import React from 'react';
import {
createBooking,
createCurrentUser,
createListing,
createTransaction,
createUser,
@ -20,10 +21,12 @@ describe('OrderPage', () => {
new Date(Date.UTC(2017, 5, 13))
),
listing: createListing('listing1'),
customer: createUser('customer1'),
provider: createUser('provider1'),
});
const props = {
currentUser: createCurrentUser('customer1'),
transaction,
tab: 'details',
intl: fakeIntl,

View file

@ -85,7 +85,7 @@ export const fetchSale = id =>
dispatch(fetchSaleRequest());
return sdk.transactions
.show({ id, include: ['customer', 'listing', 'booking'] }, { expand: true })
.show({ id, include: ['customer', 'provider', 'listing', 'booking'] }, { expand: true })
.then(response => {
dispatch(addEntities(response));
dispatch(fetchSaleSuccess(response));

View file

@ -1,10 +1,11 @@
import React, { PropTypes } from 'react';
import classNames from 'classnames';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { compose } from 'redux';
import { connect } from 'react-redux';
import * as propTypes from '../../util/propTypes';
import { ensureBooking, ensureListing, ensureTransaction, ensureUser } from '../../util/data';
import { Button, SaleDetailsPanel, PageLayout } from '../../components';
import { Button, NamedRedirect, SaleDetailsPanel, PageLayout } from '../../components';
import { getEntities } from '../../ducks/sdk.duck';
import { acceptSale, rejectSale, loadData } from './SalePage.duck';
@ -13,11 +14,20 @@ import css from './SalePage.css';
// SalePage handles data loading
// It show loading data text or SaleDetailsPanel (and later also another panel for messages).
export const SalePageComponent = props => {
const { intl, onAcceptSale, onRejectSale, transaction } = props;
const { currentUser, intl, onAcceptSale, onRejectSale, transaction } = props;
const currentTransaction = ensureTransaction(transaction);
const currentListing = ensureListing(currentTransaction.listing);
const title = currentListing.attributes.title;
// Redirect users with someone else's direct link to their own inbox/sales page.
const isDataAvailable = currentUser && currentTransaction.id && currentTransaction.provider;
const isOwnSale = isDataAvailable && currentUser.id.uuid === currentTransaction.provider.id.uuid;
if (isDataAvailable && !isOwnSale) {
// eslint-disable-next-line no-console
console.error('Tried to access a sale that was not owned by the current user');
return <NamedRedirect name="InboxPage" params={{ tab: 'sales' }} />;
}
const detailsProps = {
subtotalPrice: currentTransaction.attributes.total,
commission: currentTransaction.attributes.commission,
@ -32,11 +42,13 @@ export const SalePageComponent = props => {
[css.tabContentVisible]: props.tab === 'details',
});
const panel = currentTransaction.id
const panel = isDataAvailable && currentTransaction.id
? <SaleDetailsPanel className={detailsClassName} {...detailsProps} />
: <h1 className={css.title}><FormattedMessage id="SalePage.loadingData" /></h1>;
const actionButtons = currentTransaction.attributes.state === propTypes.TX_STATE_PREAUTHORIZED
const isPreauthorizedState = currentTransaction.attributes.state ===
propTypes.TX_STATE_PREAUTHORIZED;
const actionButtons = isDataAvailable && isPreauthorizedState
? <div className={css.actionButtons}>
<Button className={css.rejectButton} onClick={() => onRejectSale(currentTransaction.id)}>
<FormattedMessage id="SalePage.rejectButton" />
@ -55,11 +67,12 @@ export const SalePageComponent = props => {
);
};
SalePageComponent.defaultProps = { transaction: null };
SalePageComponent.defaultProps = { transaction: null, currentUser: null };
const { func, oneOf } = PropTypes;
SalePageComponent.propTypes = {
currentUser: propTypes.currentUser,
intl: intlShape.isRequired,
onAcceptSale: func.isRequired,
onRejectSale: func.isRequired,
@ -85,7 +98,9 @@ const mapDispatchToProps = dispatch => {
};
};
const SalePage = connect(mapStateToProps, mapDispatchToProps)(injectIntl(SalePageComponent));
const SalePage = compose(connect(mapStateToProps, mapDispatchToProps), injectIntl)(
SalePageComponent
);
SalePage.loadData = loadData;

View file

@ -1,6 +1,7 @@
import React from 'react';
import {
createBooking,
createCurrentUser,
createListing,
createTransaction,
createUser,
@ -21,9 +22,11 @@ describe('SalePage', () => {
),
listing: createListing('listing1'),
customer: createUser('customer1'),
provider: createUser('provider1'),
});
const props = {
currentUser: createCurrentUser('provider1'),
onAcceptSale: () => {},
onRejectSale: () => {},
transaction,