mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-26 06:47:17 +10:00
622 lines
22 KiB
JavaScript
622 lines
22 KiB
JavaScript
import pick from 'lodash/pick';
|
|
import pickBy from 'lodash/pickBy';
|
|
import isEmpty from 'lodash/isEmpty';
|
|
import moment from 'moment';
|
|
import config from '../../config';
|
|
import { types as sdkTypes } from '../../util/sdkLoader';
|
|
import { isTransactionsTransitionInvalidTransition, storableError } from '../../util/errors';
|
|
import {
|
|
txIsEnquired,
|
|
getReview1Transition,
|
|
getReview2Transition,
|
|
txIsInFirstReviewBy,
|
|
TRANSITION_ACCEPT,
|
|
TRANSITION_DECLINE,
|
|
} from '../../util/transaction';
|
|
import * as log from '../../util/log';
|
|
import {
|
|
updatedEntities,
|
|
denormalisedEntities,
|
|
denormalisedResponseEntities,
|
|
} from '../../util/data';
|
|
import { addMarketplaceEntities } from '../../ducks/marketplaceData.duck';
|
|
import { fetchCurrentUserNotifications } from '../../ducks/user.duck';
|
|
|
|
const { UUID } = sdkTypes;
|
|
|
|
const MESSAGES_PAGE_SIZE = 100;
|
|
const CUSTOMER = 'customer';
|
|
|
|
// ================ Action types ================ //
|
|
|
|
export const SET_INITAL_VALUES = 'app/TransactionPage/SET_INITIAL_VALUES';
|
|
|
|
export const FETCH_TRANSACTION_REQUEST = 'app/TransactionPage/FETCH_TRANSACTION_REQUEST';
|
|
export const FETCH_TRANSACTION_SUCCESS = 'app/TransactionPage/FETCH_TRANSACTION_SUCCESS';
|
|
export const FETCH_TRANSACTION_ERROR = 'app/TransactionPage/FETCH_TRANSACTION_ERROR';
|
|
|
|
export const FETCH_TRANSITIONS_REQUEST = 'app/TransactionPage/FETCH_TRANSITIONS_REQUEST';
|
|
export const FETCH_TRANSITIONS_SUCCESS = 'app/TransactionPage/FETCH_TRANSITIONS_SUCCESS';
|
|
export const FETCH_TRANSITIONS_ERROR = 'app/TransactionPage/FETCH_TRANSITIONS_ERROR';
|
|
|
|
export const ACCEPT_SALE_REQUEST = 'app/TransactionPage/ACCEPT_SALE_REQUEST';
|
|
export const ACCEPT_SALE_SUCCESS = 'app/TransactionPage/ACCEPT_SALE_SUCCESS';
|
|
export const ACCEPT_SALE_ERROR = 'app/TransactionPage/ACCEPT_SALE_ERROR';
|
|
|
|
export const DECLINE_SALE_REQUEST = 'app/TransactionPage/DECLINE_SALE_REQUEST';
|
|
export const DECLINE_SALE_SUCCESS = 'app/TransactionPage/DECLINE_SALE_SUCCESS';
|
|
export const DECLINE_SALE_ERROR = 'app/TransactionPage/DECLINE_SALE_ERROR';
|
|
|
|
export const FETCH_MESSAGES_REQUEST = 'app/TransactionPage/FETCH_MESSAGES_REQUEST';
|
|
export const FETCH_MESSAGES_SUCCESS = 'app/TransactionPage/FETCH_MESSAGES_SUCCESS';
|
|
export const FETCH_MESSAGES_ERROR = 'app/TransactionPage/FETCH_MESSAGES_ERROR';
|
|
|
|
export const SEND_MESSAGE_REQUEST = 'app/TransactionPage/SEND_MESSAGE_REQUEST';
|
|
export const SEND_MESSAGE_SUCCESS = 'app/TransactionPage/SEND_MESSAGE_SUCCESS';
|
|
export const SEND_MESSAGE_ERROR = 'app/TransactionPage/SEND_MESSAGE_ERROR';
|
|
|
|
export const SEND_REVIEW_REQUEST = 'app/TransactionPage/SEND_REVIEW_REQUEST';
|
|
export const SEND_REVIEW_SUCCESS = 'app/TransactionPage/SEND_REVIEW_SUCCESS';
|
|
export const SEND_REVIEW_ERROR = 'app/TransactionPage/SEND_REVIEW_ERROR';
|
|
|
|
export const FETCH_TIME_SLOTS_REQUEST = 'app/TransactionPage/FETCH_TIME_SLOTS_REQUEST';
|
|
export const FETCH_TIME_SLOTS_SUCCESS = 'app/TransactionPage/FETCH_TIME_SLOTS_SUCCESS';
|
|
export const FETCH_TIME_SLOTS_ERROR = 'app/TransactionPage/FETCH_TIME_SLOTS_ERROR';
|
|
|
|
// ================ Reducer ================ //
|
|
|
|
const initialState = {
|
|
fetchTransactionInProgress: false,
|
|
fetchTransactionError: null,
|
|
transactionRef: null,
|
|
acceptInProgress: false,
|
|
acceptSaleError: null,
|
|
declineInProgress: false,
|
|
declineSaleError: null,
|
|
fetchMessagesInProgress: false,
|
|
fetchMessagesError: null,
|
|
totalMessages: 0,
|
|
totalMessagePages: 0,
|
|
oldestMessagePageFetched: 0,
|
|
messages: [],
|
|
initialMessageFailedToTransaction: null,
|
|
sendMessageInProgress: false,
|
|
sendMessageError: null,
|
|
sendReviewInProgress: false,
|
|
sendReviewError: null,
|
|
timeSlots: null,
|
|
fetchTimeSlotsError: null,
|
|
fetchTransitionsInProgress: false,
|
|
fetchTransitionsError: null,
|
|
processTransitions: null,
|
|
};
|
|
|
|
// Merge entity arrays using ids, so that conflicting items in newer array (b) overwrite old values (a).
|
|
// const a = [{ id: { uuid: 1 } }, { id: { uuid: 3 } }];
|
|
// const b = [{ id: : { uuid: 2 } }, { id: : { uuid: 1 } }];
|
|
// mergeEntityArrays(a, b)
|
|
// => [{ id: { uuid: 3 } }, { id: : { uuid: 2 } }, { id: : { uuid: 1 } }]
|
|
const mergeEntityArrays = (a, b) => {
|
|
return a.filter(aEntity => !b.find(bEntity => aEntity.id.uuid === bEntity.id.uuid)).concat(b);
|
|
};
|
|
|
|
export default function checkoutPageReducer(state = initialState, action = {}) {
|
|
const { type, payload } = action;
|
|
switch (type) {
|
|
case SET_INITAL_VALUES:
|
|
return { ...initialState, ...payload };
|
|
|
|
case FETCH_TRANSACTION_REQUEST:
|
|
return { ...state, fetchTransactionInProgress: true, fetchTransactionError: null };
|
|
case FETCH_TRANSACTION_SUCCESS: {
|
|
const transactionRef = { id: payload.data.data.id, type: 'transaction' };
|
|
return { ...state, fetchTransactionInProgress: false, transactionRef };
|
|
}
|
|
case FETCH_TRANSACTION_ERROR:
|
|
console.error(payload); // eslint-disable-line
|
|
return { ...state, fetchTransactionInProgress: false, fetchTransactionError: payload };
|
|
|
|
case FETCH_TRANSITIONS_REQUEST:
|
|
return { ...state, fetchTransitionsInProgress: true, fetchTransitionsError: null };
|
|
case FETCH_TRANSITIONS_SUCCESS:
|
|
return { ...state, fetchTransitionsInProgress: false, processTransitions: payload };
|
|
case FETCH_TRANSITIONS_ERROR:
|
|
console.error(payload); // eslint-disable-line
|
|
return { ...state, fetchTransitionsInProgress: false, fetchTransitionsError: payload };
|
|
|
|
case ACCEPT_SALE_REQUEST:
|
|
return { ...state, acceptInProgress: true, acceptSaleError: null, declineSaleError: null };
|
|
case ACCEPT_SALE_SUCCESS:
|
|
return { ...state, acceptInProgress: false };
|
|
case ACCEPT_SALE_ERROR:
|
|
return { ...state, acceptInProgress: false, acceptSaleError: payload };
|
|
|
|
case DECLINE_SALE_REQUEST:
|
|
return { ...state, declineInProgress: true, declineSaleError: null, acceptSaleError: null };
|
|
case DECLINE_SALE_SUCCESS:
|
|
return { ...state, declineInProgress: false };
|
|
case DECLINE_SALE_ERROR:
|
|
return { ...state, declineInProgress: false, declineSaleError: payload };
|
|
|
|
case FETCH_MESSAGES_REQUEST:
|
|
return { ...state, fetchMessagesInProgress: true, fetchMessagesError: null };
|
|
case FETCH_MESSAGES_SUCCESS: {
|
|
const oldestMessagePageFetched =
|
|
state.oldestMessagePageFetched > payload.page
|
|
? state.oldestMessagePageFetched
|
|
: payload.page;
|
|
return {
|
|
...state,
|
|
fetchMessagesInProgress: false,
|
|
messages: mergeEntityArrays(state.messages, payload.messages),
|
|
totalMessages: payload.totalItems,
|
|
totalMessagePages: payload.totalPages,
|
|
oldestMessagePageFetched,
|
|
};
|
|
}
|
|
case FETCH_MESSAGES_ERROR:
|
|
return { ...state, fetchMessagesInProgress: false, fetchMessagesError: payload };
|
|
|
|
case SEND_MESSAGE_REQUEST:
|
|
return {
|
|
...state,
|
|
sendMessageInProgress: true,
|
|
sendMessageError: null,
|
|
initialMessageFailedToTransaction: null,
|
|
};
|
|
case SEND_MESSAGE_SUCCESS:
|
|
return { ...state, sendMessageInProgress: false };
|
|
case SEND_MESSAGE_ERROR:
|
|
return { ...state, sendMessageInProgress: false, sendMessageError: payload };
|
|
|
|
case SEND_REVIEW_REQUEST:
|
|
return { ...state, sendReviewInProgress: true, sendReviewError: null };
|
|
case SEND_REVIEW_SUCCESS:
|
|
return { ...state, sendReviewInProgress: false };
|
|
case SEND_REVIEW_ERROR:
|
|
return { ...state, sendReviewInProgress: false, sendReviewError: payload };
|
|
|
|
case FETCH_TIME_SLOTS_REQUEST:
|
|
return { ...state, fetchTimeSlotsError: null };
|
|
case FETCH_TIME_SLOTS_SUCCESS:
|
|
return { ...state, timeSlots: payload };
|
|
case FETCH_TIME_SLOTS_ERROR:
|
|
return { ...state, fetchTimeSlotsError: payload };
|
|
|
|
default:
|
|
return state;
|
|
}
|
|
}
|
|
|
|
// ================ Selectors ================ //
|
|
|
|
export const acceptOrDeclineInProgress = state => {
|
|
return state.TransactionPage.acceptInProgress || state.TransactionPage.declineInProgress;
|
|
};
|
|
|
|
// ================ Action creators ================ //
|
|
export const setInitialValues = initialValues => ({
|
|
type: SET_INITAL_VALUES,
|
|
payload: pick(initialValues, Object.keys(initialState)),
|
|
});
|
|
|
|
const fetchTransactionRequest = () => ({ type: FETCH_TRANSACTION_REQUEST });
|
|
const fetchTransactionSuccess = response => ({
|
|
type: FETCH_TRANSACTION_SUCCESS,
|
|
payload: response,
|
|
});
|
|
const fetchTransactionError = e => ({ type: FETCH_TRANSACTION_ERROR, error: true, payload: e });
|
|
|
|
const fetchTransitionsRequest = () => ({ type: FETCH_TRANSITIONS_REQUEST });
|
|
const fetchTransitionsSuccess = response => ({
|
|
type: FETCH_TRANSITIONS_SUCCESS,
|
|
payload: response,
|
|
});
|
|
const fetchTransitionsError = e => ({ type: FETCH_TRANSITIONS_ERROR, error: true, payload: e });
|
|
|
|
const acceptSaleRequest = () => ({ type: ACCEPT_SALE_REQUEST });
|
|
const acceptSaleSuccess = () => ({ type: ACCEPT_SALE_SUCCESS });
|
|
const acceptSaleError = e => ({ type: ACCEPT_SALE_ERROR, error: true, payload: e });
|
|
|
|
const declineSaleRequest = () => ({ type: DECLINE_SALE_REQUEST });
|
|
const declineSaleSuccess = () => ({ type: DECLINE_SALE_SUCCESS });
|
|
const declineSaleError = e => ({ type: DECLINE_SALE_ERROR, error: true, payload: e });
|
|
|
|
const fetchMessagesRequest = () => ({ type: FETCH_MESSAGES_REQUEST });
|
|
const fetchMessagesSuccess = (messages, pagination) => ({
|
|
type: FETCH_MESSAGES_SUCCESS,
|
|
payload: { messages, ...pagination },
|
|
});
|
|
const fetchMessagesError = e => ({ type: FETCH_MESSAGES_ERROR, error: true, payload: e });
|
|
|
|
const sendMessageRequest = () => ({ type: SEND_MESSAGE_REQUEST });
|
|
const sendMessageSuccess = () => ({ type: SEND_MESSAGE_SUCCESS });
|
|
const sendMessageError = e => ({ type: SEND_MESSAGE_ERROR, error: true, payload: e });
|
|
|
|
const sendReviewRequest = () => ({ type: SEND_REVIEW_REQUEST });
|
|
const sendReviewSuccess = () => ({ type: SEND_REVIEW_SUCCESS });
|
|
const sendReviewError = e => ({ type: SEND_REVIEW_ERROR, error: true, payload: e });
|
|
|
|
const fetchTimeSlotsRequest = () => ({ type: FETCH_TIME_SLOTS_REQUEST });
|
|
const fetchTimeSlotsSuccess = timeSlots => ({
|
|
type: FETCH_TIME_SLOTS_SUCCESS,
|
|
payload: timeSlots,
|
|
});
|
|
const fetchTimeSlotsError = e => ({
|
|
type: FETCH_TIME_SLOTS_ERROR,
|
|
error: true,
|
|
payload: e,
|
|
});
|
|
|
|
// ================ Thunks ================ //
|
|
|
|
const listingRelationship = txResponse => {
|
|
return txResponse.data.data.relationships.listing.data;
|
|
};
|
|
|
|
export const fetchTransaction = (id, txRole) => (dispatch, getState, sdk) => {
|
|
dispatch(fetchTransactionRequest());
|
|
let txResponse = null;
|
|
|
|
return sdk.transactions
|
|
.show(
|
|
{
|
|
id,
|
|
include: [
|
|
'customer',
|
|
'customer.profileImage',
|
|
'provider',
|
|
'provider.profileImage',
|
|
'listing',
|
|
'booking',
|
|
'reviews',
|
|
'reviews.author',
|
|
'reviews.subject',
|
|
],
|
|
...IMAGE_VARIANTS,
|
|
},
|
|
{ expand: true }
|
|
)
|
|
.then(response => {
|
|
txResponse = response;
|
|
const listingId = listingRelationship(response).id;
|
|
const entities = updatedEntities({}, response.data);
|
|
const listingRef = { id: listingId, type: 'listing' };
|
|
const transactionRef = { id, type: 'transaction' };
|
|
const denormalised = denormalisedEntities(entities, [listingRef, transactionRef]);
|
|
const listing = denormalised[0];
|
|
const transaction = denormalised[1];
|
|
|
|
// Fetch time slots for transactions that are in enquired state
|
|
const canFetchTimeslots =
|
|
txRole === 'customer' &&
|
|
config.enableAvailability &&
|
|
transaction &&
|
|
txIsEnquired(transaction);
|
|
|
|
if (canFetchTimeslots) {
|
|
dispatch(fetchTimeSlots(listingId));
|
|
}
|
|
|
|
const canFetchListing = listing && listing.attributes && !listing.attributes.deleted;
|
|
if (canFetchListing) {
|
|
return sdk.listings.show({
|
|
id: listingId,
|
|
include: ['author', 'author.profileImage', 'images'],
|
|
...IMAGE_VARIANTS,
|
|
});
|
|
} else {
|
|
return response;
|
|
}
|
|
})
|
|
.then(response => {
|
|
dispatch(addMarketplaceEntities(txResponse));
|
|
dispatch(addMarketplaceEntities(response));
|
|
dispatch(fetchTransactionSuccess(txResponse));
|
|
return response;
|
|
})
|
|
.catch(e => {
|
|
dispatch(fetchTransactionError(storableError(e)));
|
|
throw e;
|
|
});
|
|
};
|
|
|
|
export const acceptSale = id => (dispatch, getState, sdk) => {
|
|
if (acceptOrDeclineInProgress(getState())) {
|
|
return Promise.reject(new Error('Accept or decline already in progress'));
|
|
}
|
|
dispatch(acceptSaleRequest());
|
|
|
|
return sdk.transactions
|
|
.transition({ id, transition: TRANSITION_ACCEPT, params: {} }, { expand: true })
|
|
.then(response => {
|
|
dispatch(addMarketplaceEntities(response));
|
|
dispatch(acceptSaleSuccess());
|
|
dispatch(fetchCurrentUserNotifications());
|
|
return response;
|
|
})
|
|
.catch(e => {
|
|
dispatch(acceptSaleError(storableError(e)));
|
|
log.error(e, 'accept-sale-failed', {
|
|
txId: id,
|
|
transition: TRANSITION_ACCEPT,
|
|
});
|
|
throw e;
|
|
});
|
|
};
|
|
|
|
export const declineSale = id => (dispatch, getState, sdk) => {
|
|
if (acceptOrDeclineInProgress(getState())) {
|
|
return Promise.reject(new Error('Accept or decline already in progress'));
|
|
}
|
|
dispatch(declineSaleRequest());
|
|
|
|
return sdk.transactions
|
|
.transition({ id, transition: TRANSITION_DECLINE, params: {} }, { expand: true })
|
|
.then(response => {
|
|
dispatch(addMarketplaceEntities(response));
|
|
dispatch(declineSaleSuccess());
|
|
dispatch(fetchCurrentUserNotifications());
|
|
return response;
|
|
})
|
|
.catch(e => {
|
|
dispatch(declineSaleError(storableError(e)));
|
|
log.error(e, 'reject-sale-failed', {
|
|
txId: id,
|
|
transition: TRANSITION_DECLINE,
|
|
});
|
|
throw e;
|
|
});
|
|
};
|
|
|
|
const fetchMessages = (txId, page) => (dispatch, getState, sdk) => {
|
|
const paging = { page, per_page: MESSAGES_PAGE_SIZE };
|
|
dispatch(fetchMessagesRequest());
|
|
|
|
return sdk.messages
|
|
.query({
|
|
transaction_id: txId,
|
|
include: ['sender', 'sender.profileImage'],
|
|
...IMAGE_VARIANTS,
|
|
...paging,
|
|
})
|
|
.then(response => {
|
|
const messages = denormalisedResponseEntities(response);
|
|
const { totalItems, totalPages, page: fetchedPage } = response.data.meta;
|
|
const pagination = { totalItems, totalPages, page: fetchedPage };
|
|
const totalMessages = getState().TransactionPage.totalMessages;
|
|
|
|
// Original fetchMessages call succeeded
|
|
dispatch(fetchMessagesSuccess(messages, pagination));
|
|
|
|
// Check if totalItems has changed between fetched pagination pages
|
|
// if totalItems has changed, fetch first page again to include new incoming messages.
|
|
// TODO if there're more than 100 incoming messages,
|
|
// this should loop through most recent pages instead of fetching just the first one.
|
|
if (totalItems > totalMessages && page > 1) {
|
|
dispatch(fetchMessages(txId, 1))
|
|
.then(() => {
|
|
// Original fetch was enough as a response for user action,
|
|
// this just includes new incoming messages
|
|
})
|
|
.catch(() => {
|
|
// Background update, no need to to do anything atm.
|
|
});
|
|
}
|
|
})
|
|
.catch(e => {
|
|
dispatch(fetchMessagesError(storableError(e)));
|
|
throw e;
|
|
});
|
|
};
|
|
|
|
export const fetchMoreMessages = txId => (dispatch, getState, sdk) => {
|
|
const state = getState();
|
|
const { oldestMessagePageFetched, totalMessagePages } = state.TransactionPage;
|
|
const hasMoreOldMessages = totalMessagePages > oldestMessagePageFetched;
|
|
|
|
// In case there're no more old pages left we default to fetching the current cursor position
|
|
const nextPage = hasMoreOldMessages ? oldestMessagePageFetched + 1 : oldestMessagePageFetched;
|
|
|
|
return dispatch(fetchMessages(txId, nextPage));
|
|
};
|
|
|
|
export const sendMessage = (txId, message) => (dispatch, getState, sdk) => {
|
|
dispatch(sendMessageRequest());
|
|
|
|
return sdk.messages
|
|
.send({ transactionId: txId, content: message })
|
|
.then(response => {
|
|
const messageId = response.data.data.id;
|
|
|
|
// We fetch the first page again to add sent message to the page data
|
|
// and update possible incoming messages too.
|
|
// TODO if there're more than 100 incoming messages,
|
|
// this should loop through most recent pages instead of fetching just the first one.
|
|
return dispatch(fetchMessages(txId, 1))
|
|
.then(() => {
|
|
dispatch(sendMessageSuccess());
|
|
return messageId;
|
|
})
|
|
.catch(() => dispatch(sendMessageSuccess()));
|
|
})
|
|
.catch(e => {
|
|
dispatch(sendMessageError(storableError(e)));
|
|
// Rethrow so the page can track whether the sending failed, and
|
|
// keep the message in the form for a retry.
|
|
throw e;
|
|
});
|
|
};
|
|
|
|
const REVIEW_TX_INCLUDES = ['reviews', 'reviews.author', 'reviews.subject'];
|
|
const IMAGE_VARIANTS = {
|
|
'fields.image': [
|
|
// Profile images
|
|
'variants.square-small',
|
|
'variants.square-small2x',
|
|
|
|
// Listing images:
|
|
'variants.landscape-crop',
|
|
'variants.landscape-crop2x',
|
|
],
|
|
};
|
|
|
|
// If other party has already sent a review, we need to make transition to
|
|
// TRANSITION_REVIEW_2_BY_<CUSTOMER/PROVIDER>
|
|
const sendReviewAsSecond = (id, params, role, dispatch, sdk) => {
|
|
const transition = getReview2Transition(role === CUSTOMER);
|
|
|
|
const include = REVIEW_TX_INCLUDES;
|
|
|
|
return sdk.transactions
|
|
.transition({ id, transition, params }, { expand: true, include, ...IMAGE_VARIANTS })
|
|
.then(response => {
|
|
dispatch(addMarketplaceEntities(response));
|
|
dispatch(sendReviewSuccess());
|
|
return response;
|
|
})
|
|
.catch(e => {
|
|
dispatch(sendReviewError(storableError(e)));
|
|
|
|
// Rethrow so the page can track whether the sending failed, and
|
|
// keep the message in the form for a retry.
|
|
throw e;
|
|
});
|
|
};
|
|
|
|
// If other party has not yet sent a review, we need to make transition to
|
|
// TRANSITION_REVIEW_1_BY_<CUSTOMER/PROVIDER>
|
|
// However, the other party might have made the review after previous data synch point.
|
|
// So, error is likely to happen and then we must try another state transition
|
|
// by calling sendReviewAsSecond().
|
|
const sendReviewAsFirst = (id, params, role, dispatch, sdk) => {
|
|
const transition = getReview1Transition(role === CUSTOMER);
|
|
const include = REVIEW_TX_INCLUDES;
|
|
|
|
return sdk.transactions
|
|
.transition({ id, transition, params }, { expand: true, include, ...IMAGE_VARIANTS })
|
|
.then(response => {
|
|
dispatch(addMarketplaceEntities(response));
|
|
dispatch(sendReviewSuccess());
|
|
return response;
|
|
})
|
|
.catch(e => {
|
|
// If transaction transition is invalid, lets try another endpoint.
|
|
if (isTransactionsTransitionInvalidTransition(e)) {
|
|
return sendReviewAsSecond(id, params, role, dispatch, sdk);
|
|
} else {
|
|
dispatch(sendReviewError(storableError(e)));
|
|
|
|
// Rethrow so the page can track whether the sending failed, and
|
|
// keep the message in the form for a retry.
|
|
throw e;
|
|
}
|
|
});
|
|
};
|
|
|
|
export const sendReview = (role, tx, reviewRating, reviewContent) => (dispatch, getState, sdk) => {
|
|
const params = { reviewRating, reviewContent };
|
|
|
|
const txStateOtherPartyFirst = txIsInFirstReviewBy(tx, role !== CUSTOMER);
|
|
|
|
dispatch(sendReviewRequest());
|
|
|
|
return txStateOtherPartyFirst
|
|
? sendReviewAsSecond(tx.id, params, role, dispatch, sdk)
|
|
: sendReviewAsFirst(tx.id, params, role, dispatch, sdk);
|
|
};
|
|
|
|
const isNonEmpty = value => {
|
|
return typeof value === 'object' || Array.isArray(value) ? !isEmpty(value) : !!value;
|
|
};
|
|
|
|
const timeSlotsRequest = params => (dispatch, getState, sdk) => {
|
|
return sdk.timeslots.query(params).then(response => {
|
|
return denormalisedResponseEntities(response);
|
|
});
|
|
};
|
|
|
|
const fetchTimeSlots = listingId => (dispatch, getState, sdk) => {
|
|
dispatch(fetchTimeSlotsRequest);
|
|
|
|
// Time slots can be fetched for 90 days at a time,
|
|
// for at most 180 days from now. If max number of bookable
|
|
// day exceeds 90, a second request is made.
|
|
|
|
const maxTimeSlots = 90;
|
|
// booking range: today + bookable days -1
|
|
const bookingRange = config.dayCountAvailableForBooking - 1;
|
|
const timeSlotsRange = Math.min(bookingRange, maxTimeSlots);
|
|
|
|
const start = moment
|
|
.utc()
|
|
.startOf('day')
|
|
.toDate();
|
|
const end = moment()
|
|
.utc()
|
|
.startOf('day')
|
|
.add(timeSlotsRange, 'days')
|
|
.toDate();
|
|
const params = { listingId, start, end };
|
|
|
|
return dispatch(timeSlotsRequest(params))
|
|
.then(timeSlots => {
|
|
const secondRequest = bookingRange > maxTimeSlots;
|
|
|
|
if (secondRequest) {
|
|
const secondRange = Math.min(maxTimeSlots, bookingRange - maxTimeSlots);
|
|
const secondParams = {
|
|
listingId,
|
|
start: end,
|
|
end: moment(end)
|
|
.add(secondRange, 'days')
|
|
.toDate(),
|
|
};
|
|
|
|
return dispatch(timeSlotsRequest(secondParams)).then(secondBatch => {
|
|
const combined = timeSlots.concat(secondBatch);
|
|
dispatch(fetchTimeSlotsSuccess(combined));
|
|
});
|
|
} else {
|
|
dispatch(fetchTimeSlotsSuccess(timeSlots));
|
|
}
|
|
})
|
|
.catch(e => {
|
|
dispatch(fetchTimeSlotsError(storableError(e)));
|
|
});
|
|
};
|
|
|
|
export const fetchNextTransitions = id => (dispatch, getState, sdk) => {
|
|
dispatch(fetchTransitionsRequest());
|
|
|
|
return sdk.processTransitions
|
|
.query({ transactionId: id })
|
|
.then(res => {
|
|
dispatch(fetchTransitionsSuccess(res.data.data));
|
|
})
|
|
.catch(e => {
|
|
dispatch(fetchTransitionsError(storableError(e)));
|
|
});
|
|
};
|
|
|
|
// 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, getState) => {
|
|
const txId = new UUID(params.id);
|
|
const state = getState().TransactionPage;
|
|
const txRef = state.transactionRef;
|
|
const txRole = params.transactionRole;
|
|
|
|
// In case a transaction reference is found from a previous
|
|
// data load -> clear the state. Otherwise keep the non-null
|
|
// and non-empty values which may have been set from a previous page.
|
|
const initialValues = txRef ? {} : pickBy(state, isNonEmpty);
|
|
dispatch(setInitialValues(initialValues));
|
|
|
|
// Sale / order (i.e. transaction entity in API)
|
|
return Promise.all([
|
|
dispatch(fetchTransaction(txId, txRole)),
|
|
dispatch(fetchMessages(txId, 1)),
|
|
dispatch(fetchNextTransitions(txId)),
|
|
]);
|
|
};
|