diff --git a/src/containers/InboxPage/InboxPage.css b/src/containers/InboxPage/InboxPage.css index 5aa94ed1..4ab2be16 100644 --- a/src/containers/InboxPage/InboxPage.css +++ b/src/containers/InboxPage/InboxPage.css @@ -14,3 +14,48 @@ font-weight: bold; text-decoration: underline; } + +.listItem { + border-bottom: 1px solid #979797; + + &:last-child { + border-bottom: none; + } +} + +.itemLink { + display: flex; + padding: 1rem; + text-decoration: none; +} + +.itemAvatar { + margin-right: 1rem; + width: 44px; + height: 44px; +} + +.itemAvatarImage { + border-radius: 50%; +} + +.itemInfo { + flex: 1; +} + +.itemUsername { + font-weight: bold; + font-size: 16px; + text-decoration: none; +} + +.itemTimestamp { + float: right; + font-size: 14px; +} + +.itemState { + margin-top: 5px; + font-size: 14px; + text-decoration: none; +} diff --git a/src/containers/InboxPage/InboxPage.duck.js b/src/containers/InboxPage/InboxPage.duck.js new file mode 100644 index 00000000..75c68886 --- /dev/null +++ b/src/containers/InboxPage/InboxPage.duck.js @@ -0,0 +1,96 @@ +import _ from 'lodash'; +import { addEntities } from '../../ducks/sdk.duck'; + +// ================ Action types ================ // + +export const FETCH_ORDERS_OR_SALES_REQUEST = 'app/InboxPage/FETCH_ORDERS_OR_SALES_REQUEST'; +export const FETCH_ORDERS_OR_SALES_SUCCESS = 'app/InboxPage/FETCH_ORDERS_OR_SALES_SUCCESS'; +export const FETCH_ORDERS_OR_SALES_ERROR = 'app/InboxPage/FETCH_ORDERS_OR_SALES_ERROR'; + +// ================ Reducer ================ // + +const entityRefs = entities => + entities.map(entity => ({ + id: entity.id, + type: entity.type, + })); + +const initialState = { + fetchInProgress: false, + fetchOrdersOrSalesError: null, + transactionRefs: [], +}; + +export default function checkoutPageReducer(state = initialState, action = {}) { + const { type, payload } = action; + switch (type) { + case FETCH_ORDERS_OR_SALES_REQUEST: + return { ...state, fetchInProgress: true, fetchOrdersOrSalesError: null }; + case FETCH_ORDERS_OR_SALES_SUCCESS: + return { ...state, fetchInProgress: false, transactionRefs: entityRefs(payload) }; + case FETCH_ORDERS_OR_SALES_ERROR: + console.error(payload); // eslint-disable-line + return { ...state, fetchInProgress: false, fetchOrdersOrSalesError: payload }; + + default: + return state; + } +} + +// ================ Action creators ================ // + +const fetchOrdersOrSalesRequest = () => ({ type: FETCH_ORDERS_OR_SALES_REQUEST }); +const fetchOrdersOrSalesSuccess = transactions => ({ + type: FETCH_ORDERS_OR_SALES_SUCCESS, + payload: transactions, +}); +const fetchOrdersOrSalesError = e => ({ + type: FETCH_ORDERS_OR_SALES_ERROR, + error: true, + payload: e, +}); + +// ================ Thunks ================ // + +const sortedTransactions = txs => + _.chain(txs) + .sortBy(tx => { + return tx.attributes ? tx.attributes.lastTransitionedAt : null; + }) + .reverse() + .value(); + +export const loadData = params => + (dispatch, getState, sdk) => { + const { tab } = params; + + const onlyFilterValues = { + orders: 'order', + sales: 'sale', + }; + + const onlyFilter = onlyFilterValues[tab]; + if (!onlyFilter) { + return Promise.reject(new Error(`Invalid tab for InboxPage: ${tab}`)); + } + + dispatch(fetchOrdersOrSalesRequest()); + + const queryParams = { + only: onlyFilter, + include: ['provider', 'customer'], + }; + + return sdk.transactions + .query(queryParams) + .then(response => { + const transactions = sortedTransactions(response.data.data); + dispatch(addEntities(response)); + dispatch(fetchOrdersOrSalesSuccess(transactions)); + return response; + }) + .catch(e => { + dispatch(fetchOrdersOrSalesError(e)); + throw e; + }); + }; diff --git a/src/containers/InboxPage/InboxPage.js b/src/containers/InboxPage/InboxPage.js index 75606155..458a2ef1 100644 --- a/src/containers/InboxPage/InboxPage.js +++ b/src/containers/InboxPage/InboxPage.js @@ -1,15 +1,101 @@ import React, { PropTypes } from 'react'; +import { compose } from 'redux'; +import { connect } from 'react-redux'; import { FormattedMessage, injectIntl, intlShape } from 'react-intl'; -import { PageLayout, NamedLink, H1 } from '../../components'; +import { PageLayout, NamedRedirect, NamedLink, H1 } from '../../components'; +import * as propTypes from '../../util/propTypes'; +import { getEntities } from '../../ducks/sdk.duck'; +import { loadData } from './InboxPage.duck'; import css from './InboxPage.css'; +const { shape, string, arrayOf, bool, oneOf } = PropTypes; + +// Formatted username +const username = user => { + const profile = user && user.attributes && user.attributes.profile + ? user.attributes.profile + : null; + return profile ? `${profile.firstName} ${profile.lastName}` : ''; +}; + +// Localised timestamp when the given transaction was updated +const timestamp = (intl, tx) => { + const date = tx.attributes ? tx.attributes.lastTransitionedAt : null; + return date ? intl.formatDate(date) : ''; +}; + +// Translated name of the state of the given transaction +const txState = (intl, tx) => { + const { state } = tx; + if (state === propTypes.TX_STATE_ACCEPTED) { + return intl.formatMessage({ + id: 'InboxPage.stateAccepted', + }); + } else if (state === propTypes.TX_STATE_REJECTED) { + return intl.formatMessage({ + id: 'InboxPage.stateRejected', + }); + } + return intl.formatMessage({ + id: 'InboxPage.statePending', + }); +}; + +const InboxItem = props => { + const { type, tx, intl } = props; + const { customer, provider } = tx; + const isOrder = type === 'order'; + const otherUserName = username(isOrder ? provider : customer); + const otherUserAvatar = 'https://placehold.it/44x44'; + return ( + +
+ {otherUserName} +
+
+
+ {otherUserName} + {timestamp(intl, tx)} +
+
{txState(intl, tx)}
+
+
+ ); +}; + +InboxItem.propTypes = { + type: oneOf(['order', 'sale']).isRequired, + tx: propTypes.transaction.isRequired, + intl: intlShape.isRequired, +}; + export const InboxPageComponent = props => { - const { tab, intl } = props; + const { fetchInProgress, transactions, intl, params } = props; + const { tab } = params; + + const validTab = tab === 'orders' || tab === 'sales'; + if (!validTab) { + return ; + } + + const isOrders = tab === 'orders'; const ordersTitle = intl.formatMessage({ id: 'InboxPage.ordersTitle' }); const salesTitle = intl.formatMessage({ id: 'InboxPage.salesTitle' }); - const title = tab === 'orders' ? ordersTitle : salesTitle; + const title = isOrders ? ordersTitle : salesTitle; + + const toTxItem = tx => { + return ( +
  • + +
  • + ); + }; return ( @@ -17,26 +103,53 @@ export const InboxPageComponent = props => { +
      + {!fetchInProgress ? transactions.map(toTxItem) : null} +
    ); }; -const { oneOf } = PropTypes; - InboxPageComponent.propTypes = { - tab: oneOf(['orders', 'sales']).isRequired, + params: shape({ + tab: string.isRequired, + }).isRequired, + + fetchInProgress: bool.isRequired, + transactions: arrayOf(propTypes.transaction).isRequired, // from injectIntl intl: intlShape.isRequired, }; -const InboxPage = injectIntl(InboxPageComponent); +const mapStateToProps = state => { + const marketplaceData = state.data; + const refs = state.InboxPage.transactionRefs; + return { + fetchInProgress: state.InboxPage.fetchInProgress, + transactions: getEntities(marketplaceData, refs), + }; +}; + +const InboxPage = compose(connect(mapStateToProps), injectIntl)(InboxPageComponent); + +InboxPage.loadData = loadData; export default InboxPage; diff --git a/src/containers/InboxPage/InboxPage.test.js b/src/containers/InboxPage/InboxPage.test.js index ab40c7c8..e2eff6e9 100644 --- a/src/containers/InboxPage/InboxPage.test.js +++ b/src/containers/InboxPage/InboxPage.test.js @@ -6,7 +6,11 @@ import { InboxPageComponent } from './InboxPage'; describe('InboxPage', () => { it('matches snapshot', () => { const props = { - tab: 'orders', + params: { + tab: 'orders', + }, + fetchInProgress: false, + transactions: [], intl: fakeIntl, }; const tree = renderShallow(); diff --git a/src/containers/InboxPage/__snapshots__/InboxPage.test.js.snap b/src/containers/InboxPage/__snapshots__/InboxPage.test.js.snap index 8347e444..b0f6d351 100644 --- a/src/containers/InboxPage/__snapshots__/InboxPage.test.js.snap +++ b/src/containers/InboxPage/__snapshots__/InboxPage.test.js.snap @@ -9,17 +9,29 @@ exports[`InboxPage matches snapshot 1`] = ` +