mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-28 20:53:24 +10:00
Merge pull request #562 from sharetribe/tx-message-paging
Order message paging
This commit is contained in:
commit
f3fc4acd7c
7 changed files with 98 additions and 14 deletions
|
|
@ -90,11 +90,14 @@ export class OrderDetailsPanelComponent extends Component {
|
|||
className,
|
||||
currentUser,
|
||||
transaction,
|
||||
totalMessages,
|
||||
messages,
|
||||
initialMessageFailed,
|
||||
fetchMessagesInProgress,
|
||||
fetchMessagesError,
|
||||
sendMessageInProgress,
|
||||
sendMessageError,
|
||||
onShowMoreMessages,
|
||||
onSendMessage,
|
||||
onResetForm,
|
||||
intl,
|
||||
|
|
@ -170,8 +173,10 @@ export class OrderDetailsPanelComponent extends Component {
|
|||
const txTransitions = currentTransaction.attributes.transitions
|
||||
? currentTransaction.attributes.transitions
|
||||
: [];
|
||||
const hasOlderMessages = false; // TODO
|
||||
const showOlderMessages = () => null; // TODO
|
||||
const hasOlderMessages = totalMessages > messages.length;
|
||||
const handleShowOlderMessages = () => {
|
||||
onShowMoreMessages(currentTransaction.id);
|
||||
};
|
||||
const showFeed =
|
||||
messages.length > 0 || txTransitions.length > 0 || initialMessageFailed || fetchMessagesError;
|
||||
const feedContainer = showFeed ? (
|
||||
|
|
@ -194,8 +199,9 @@ export class OrderDetailsPanelComponent extends Component {
|
|||
messages={messages}
|
||||
transaction={currentTransaction}
|
||||
currentUser={currentUser}
|
||||
hasOlderMessages={hasOlderMessages}
|
||||
onShowOlderMessages={showOlderMessages}
|
||||
hasOlderMessages={hasOlderMessages && !fetchMessagesInProgress}
|
||||
onShowOlderMessages={handleShowOlderMessages}
|
||||
fetchMessagesInProgress={fetchMessagesInProgress}
|
||||
/>
|
||||
</div>
|
||||
) : null;
|
||||
|
|
@ -337,7 +343,7 @@ OrderDetailsPanelComponent.defaultProps = {
|
|||
sendMessageError: null,
|
||||
};
|
||||
|
||||
const { string, arrayOf, bool, func } = PropTypes;
|
||||
const { string, arrayOf, bool, func, number } = PropTypes;
|
||||
|
||||
OrderDetailsPanelComponent.propTypes = {
|
||||
rootClassName: string,
|
||||
|
|
@ -345,11 +351,14 @@ OrderDetailsPanelComponent.propTypes = {
|
|||
|
||||
currentUser: propTypes.currentUser,
|
||||
transaction: propTypes.transaction.isRequired,
|
||||
totalMessages: number.isRequired,
|
||||
messages: arrayOf(propTypes.message).isRequired,
|
||||
initialMessageFailed: bool.isRequired,
|
||||
fetchMessagesInProgress: bool.isRequired,
|
||||
fetchMessagesError: propTypes.error,
|
||||
sendMessageInProgress: bool.isRequired,
|
||||
sendMessageError: propTypes.error,
|
||||
onShowMoreMessages: func.isRequired,
|
||||
onSendMessage: func.isRequired,
|
||||
onResetForm: func.isRequired,
|
||||
|
||||
|
|
|
|||
|
|
@ -70,12 +70,15 @@ describe('OrderDetailsPanel', () => {
|
|||
const panelBaseProps = {
|
||||
intl: fakeIntl,
|
||||
currentUser: createCurrentUser('user2'),
|
||||
totalMessages: 2,
|
||||
messages: [
|
||||
createMessage('msg1', {}, { sender: createUser('user1') }),
|
||||
createMessage('msg2', {}, { sender: createUser('user2') }),
|
||||
],
|
||||
initialMessageFailed: false,
|
||||
fetchMessagesInProgress: false,
|
||||
sendMessageInProgress: false,
|
||||
onShowMoreMessages: noop,
|
||||
onSendMessage: noop,
|
||||
onResetForm: noop,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@ exports[`OrderDetailsPanel accepted matches snapshot 1`] = `
|
|||
"type": "currentUser",
|
||||
}
|
||||
}
|
||||
fetchMessagesInProgress={false}
|
||||
hasOlderMessages={false}
|
||||
messages={
|
||||
Array [
|
||||
|
|
@ -884,6 +885,7 @@ exports[`OrderDetailsPanel autodeclined matches snapshot 1`] = `
|
|||
"type": "currentUser",
|
||||
}
|
||||
}
|
||||
fetchMessagesInProgress={false}
|
||||
hasOlderMessages={false}
|
||||
messages={
|
||||
Array [
|
||||
|
|
@ -1527,6 +1529,7 @@ exports[`OrderDetailsPanel canceled matches snapshot 1`] = `
|
|||
"type": "currentUser",
|
||||
}
|
||||
}
|
||||
fetchMessagesInProgress={false}
|
||||
hasOlderMessages={false}
|
||||
messages={
|
||||
Array [
|
||||
|
|
@ -2158,6 +2161,7 @@ exports[`OrderDetailsPanel declined matches snapshot 1`] = `
|
|||
"type": "currentUser",
|
||||
}
|
||||
}
|
||||
fetchMessagesInProgress={false}
|
||||
hasOlderMessages={false}
|
||||
messages={
|
||||
Array [
|
||||
|
|
@ -2789,6 +2793,7 @@ exports[`OrderDetailsPanel delivered matches snapshot 1`] = `
|
|||
"type": "currentUser",
|
||||
}
|
||||
}
|
||||
fetchMessagesInProgress={false}
|
||||
hasOlderMessages={false}
|
||||
messages={
|
||||
Array [
|
||||
|
|
@ -3443,6 +3448,7 @@ exports[`OrderDetailsPanel preauthorized matches snapshot 1`] = `
|
|||
"type": "currentUser",
|
||||
}
|
||||
}
|
||||
fetchMessagesInProgress={false}
|
||||
hasOlderMessages={false}
|
||||
messages={
|
||||
Array [
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { storableError } from '../../util/errors';
|
|||
import { addMarketplaceEntities } from '../../ducks/marketplaceData.duck';
|
||||
import { updatedEntities, denormalisedEntities } from '../../util/data';
|
||||
|
||||
const MESSAGES_PAGE_SIZE = 100;
|
||||
|
||||
// ================ Action types ================ //
|
||||
|
||||
export const SET_INITAL_VALUES = 'app/OrderPage/SET_INITIAL_VALUES';
|
||||
|
|
@ -28,6 +30,7 @@ const initialState = {
|
|||
transactionRef: null,
|
||||
fetchMessagesInProgress: false,
|
||||
fetchMessagesError: null,
|
||||
totalMessages: 0,
|
||||
messages: [],
|
||||
messageSendingFailedToTransaction: null,
|
||||
sendMessageInProgress: false,
|
||||
|
|
@ -53,7 +56,12 @@ export default function checkoutPageReducer(state = initialState, action = {}) {
|
|||
case FETCH_MESSAGES_REQUEST:
|
||||
return { ...state, fetchMessagesInProgress: true, fetchMessagesError: null };
|
||||
case FETCH_MESSAGES_SUCCESS:
|
||||
return { ...state, fetchMessagesInProgress: false, messages: payload };
|
||||
return {
|
||||
...state,
|
||||
fetchMessagesInProgress: false,
|
||||
messages: payload.messages,
|
||||
totalMessages: payload.totalItems,
|
||||
};
|
||||
case FETCH_MESSAGES_ERROR:
|
||||
return { ...state, fetchMessagesInProgress: false, fetchMessagesError: payload };
|
||||
|
||||
|
|
@ -69,6 +77,12 @@ export default function checkoutPageReducer(state = initialState, action = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// ================ Selectors ================ //
|
||||
|
||||
export const fetchedMessagesCount = state => {
|
||||
return state.OrderPage.messages.length;
|
||||
};
|
||||
|
||||
// ================ Action creators ================ //
|
||||
|
||||
export const setInitialValues = initialValues => ({
|
||||
|
|
@ -81,7 +95,10 @@ const fetchOrderSuccess = response => ({ type: FETCH_ORDER_SUCCESS, payload: res
|
|||
const fetchOrderError = e => ({ type: FETCH_ORDER_ERROR, error: true, payload: e });
|
||||
|
||||
const fetchMessagesRequest = () => ({ type: FETCH_MESSAGES_REQUEST });
|
||||
const fetchMessagesSuccess = messages => ({ type: FETCH_MESSAGES_SUCCESS, payload: messages });
|
||||
const fetchMessagesSuccess = (messages, totalItems) => ({
|
||||
type: FETCH_MESSAGES_SUCCESS,
|
||||
payload: { messages, totalItems },
|
||||
});
|
||||
const fetchMessagesError = e => ({ type: FETCH_MESSAGES_ERROR, error: true, payload: e });
|
||||
|
||||
const sendMessageRequest = () => ({ type: SEND_MESSAGE_REQUEST });
|
||||
|
|
@ -132,17 +149,17 @@ export const fetchOrder = id => (dispatch, getState, sdk) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const fetchMessages = txId => (dispatch, getState, sdk) => {
|
||||
const fetchMessages = (txId, paging) => (dispatch, getState, sdk) => {
|
||||
dispatch(fetchMessagesRequest());
|
||||
|
||||
return sdk.messages
|
||||
.query({ transaction_id: txId, include: ['sender', 'sender.profileImage'] })
|
||||
.query({ transaction_id: txId, include: ['sender', 'sender.profileImage'], ...paging })
|
||||
.then(response => {
|
||||
const entities = updatedEntities({}, response.data);
|
||||
const messageIds = response.data.data.map(d => d.id);
|
||||
const denormalized = denormalisedEntities(entities, 'message', messageIds);
|
||||
|
||||
dispatch(fetchMessagesSuccess(denormalized));
|
||||
dispatch(fetchMessagesSuccess(denormalized, response.data.meta.totalItems));
|
||||
})
|
||||
.catch(e => {
|
||||
dispatch(fetchMessagesError(storableError(e)));
|
||||
|
|
@ -150,6 +167,25 @@ export const fetchMessages = txId => (dispatch, getState, sdk) => {
|
|||
});
|
||||
};
|
||||
|
||||
const fetchNLatestMessages = (txId, n) => (dispatch, getState, sdk) => {
|
||||
const paging = {
|
||||
page: 1,
|
||||
per_page: n,
|
||||
};
|
||||
return dispatch(fetchMessages(txId, paging));
|
||||
};
|
||||
|
||||
export const fetchMoreMessages = txId => (dispatch, getState, sdk) => {
|
||||
// This is clearly not the most sophisticated solution, but the
|
||||
// default page size should be large enough that seeing the "Show
|
||||
// older" button is very rare.
|
||||
//
|
||||
// This compromises on the network request size in favor of correct
|
||||
// page offset handling that is quite tricky.
|
||||
const messagesToFetch = fetchedMessagesCount(getState()) + MESSAGES_PAGE_SIZE;
|
||||
return dispatch(fetchNLatestMessages(txId, messagesToFetch));
|
||||
};
|
||||
|
||||
// loadData is a collection of async calls that need to be made
|
||||
// before page has all the info it needs to render itself
|
||||
export const loadData = params => dispatch => {
|
||||
|
|
@ -159,7 +195,10 @@ export const loadData = params => dispatch => {
|
|||
dispatch(setInitialValues({ sendMessageError: null }));
|
||||
|
||||
// Order (i.e. transaction entity in API, but from buyers perspective) contains order details
|
||||
return Promise.all([dispatch(fetchOrder(orderId)), dispatch(fetchMessages(orderId))]);
|
||||
return Promise.all([
|
||||
dispatch(fetchOrder(orderId)),
|
||||
dispatch(fetchNLatestMessages(orderId, MESSAGES_PAGE_SIZE)),
|
||||
]);
|
||||
};
|
||||
|
||||
export const sendMessage = (orderId, message) => (dispatch, getState, sdk) => {
|
||||
|
|
@ -169,7 +208,14 @@ export const sendMessage = (orderId, message) => (dispatch, getState, sdk) => {
|
|||
.send({ transactionId: orderId, content: message })
|
||||
.then(response => {
|
||||
const messageId = response.data.data.id;
|
||||
return dispatch(fetchMessages(orderId))
|
||||
|
||||
// Try to keep the fetched messages in the store by fetching the
|
||||
// sent message and as much messages as there were before. Some
|
||||
// of the older ones might be lost if there are also other new
|
||||
// messages received in addition to this message.
|
||||
const messagesToFetch = fetchedMessagesCount(getState()) + 1;
|
||||
|
||||
return dispatch(fetchNLatestMessages(orderId, messagesToFetch))
|
||||
.then(() => {
|
||||
dispatch(sendMessageSuccess());
|
||||
return messageId;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import {
|
|||
} from '../../components';
|
||||
import { TopbarContainer } from '../../containers';
|
||||
|
||||
import { loadData, setInitialValues, sendMessage } from './OrderPage.duck';
|
||||
import { loadData, setInitialValues, sendMessage, fetchMoreMessages } from './OrderPage.duck';
|
||||
import css from './OrderPage.css';
|
||||
|
||||
// OrderPage handles data loading
|
||||
|
|
@ -30,11 +30,14 @@ export const OrderPageComponent = props => {
|
|||
const {
|
||||
currentUser,
|
||||
fetchOrderError,
|
||||
fetchMessagesInProgress,
|
||||
fetchMessagesError,
|
||||
totalMessages,
|
||||
messages,
|
||||
messageSendingFailedToTransaction,
|
||||
sendMessageInProgress,
|
||||
sendMessageError,
|
||||
onShowMoreMessages,
|
||||
onSendMessage,
|
||||
onResetForm,
|
||||
intl,
|
||||
|
|
@ -86,11 +89,14 @@ export const OrderPageComponent = props => {
|
|||
className={detailsClassName}
|
||||
currentUser={currentUser}
|
||||
transaction={currentTransaction}
|
||||
fetchMessagesInProgress={fetchMessagesInProgress}
|
||||
totalMessages={totalMessages}
|
||||
messages={messages}
|
||||
initialMessageFailed={initialMessageFailed}
|
||||
fetchMessagesError={fetchMessagesError}
|
||||
sendMessageInProgress={sendMessageInProgress}
|
||||
sendMessageError={sendMessageError}
|
||||
onShowMoreMessages={onShowMoreMessages}
|
||||
onSendMessage={onSendMessage}
|
||||
onResetForm={onResetForm}
|
||||
/>
|
||||
|
|
@ -125,7 +131,7 @@ OrderPageComponent.defaultProps = {
|
|||
transaction: null,
|
||||
};
|
||||
|
||||
const { bool, oneOf, shape, string, array, func } = PropTypes;
|
||||
const { bool, oneOf, shape, string, array, func, number } = PropTypes;
|
||||
|
||||
OrderPageComponent.propTypes = {
|
||||
params: shape({ id: string }).isRequired,
|
||||
|
|
@ -133,13 +139,16 @@ OrderPageComponent.propTypes = {
|
|||
|
||||
currentUser: propTypes.currentUser,
|
||||
fetchOrderError: propTypes.error,
|
||||
fetchMessagesInProgress: bool.isRequired,
|
||||
fetchMessagesError: propTypes.error,
|
||||
totalMessages: number.isRequired,
|
||||
messages: array.isRequired,
|
||||
messageSendingFailedToTransaction: propTypes.uuid,
|
||||
sendMessageInProgress: bool.isRequired,
|
||||
sendMessageError: propTypes.error,
|
||||
scrollingDisabled: bool.isRequired,
|
||||
transaction: propTypes.transaction,
|
||||
onShowMoreMessages: func.isRequired,
|
||||
onSendMessage: func.isRequired,
|
||||
onResetForm: func.isRequired,
|
||||
|
||||
|
|
@ -152,7 +161,9 @@ const mapStateToProps = state => {
|
|||
const {
|
||||
fetchOrderError,
|
||||
transactionRef,
|
||||
fetchMessagesInProgress,
|
||||
fetchMessagesError,
|
||||
totalMessages,
|
||||
messages,
|
||||
messageSendingFailedToTransaction,
|
||||
sendMessageInProgress,
|
||||
|
|
@ -164,7 +175,9 @@ const mapStateToProps = state => {
|
|||
return {
|
||||
currentUser,
|
||||
fetchOrderError,
|
||||
fetchMessagesInProgress,
|
||||
fetchMessagesError,
|
||||
totalMessages,
|
||||
messages,
|
||||
messageSendingFailedToTransaction,
|
||||
sendMessageInProgress,
|
||||
|
|
@ -175,6 +188,7 @@ const mapStateToProps = state => {
|
|||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onShowMoreMessages: orderId => dispatch(fetchMoreMessages(orderId)),
|
||||
onSendMessage: (orderId, message) => dispatch(sendMessage(orderId, message)),
|
||||
onResetForm: formName => dispatch(resetForm(formName)),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,10 +33,13 @@ describe('OrderPage', () => {
|
|||
tab: 'details',
|
||||
|
||||
currentUser: createCurrentUser('customer1'),
|
||||
totalMessages: 0,
|
||||
messages: [],
|
||||
fetchMessagesInProgress: false,
|
||||
sendMessageInProgress: false,
|
||||
scrollingDisabled: false,
|
||||
transaction,
|
||||
onShowMoreMessages: noop,
|
||||
onSendMessage: noop,
|
||||
onResetForm: noop,
|
||||
|
||||
|
|
|
|||
|
|
@ -42,12 +42,15 @@ exports[`OrderPage matches snapshot 1`] = `
|
|||
}
|
||||
}
|
||||
fetchMessagesError={null}
|
||||
fetchMessagesInProgress={false}
|
||||
initialMessageFailed={false}
|
||||
messages={Array []}
|
||||
onResetForm={[Function]}
|
||||
onSendMessage={[Function]}
|
||||
onShowMoreMessages={[Function]}
|
||||
sendMessageError={null}
|
||||
sendMessageInProgress={false}
|
||||
totalMessages={0}
|
||||
transaction={
|
||||
Object {
|
||||
"attributes": Object {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue