Merge pull request #152 from sharetribe/topbar-inbox-link

Inbox links
This commit is contained in:
Kimmo Puputti 2017-05-09 15:07:11 +03:00 committed by GitHub
commit 389e376abf
9 changed files with 121 additions and 71 deletions

View file

@ -9,7 +9,7 @@ import { Avatar, InlineButton, NamedLink } from '../../components';
import css from './MobileMenu.css';
const MobileMenu = props => {
const { isAuthenticated, name, onLogout } = props;
const { isAuthenticated, currentUserHasListings, name, onLogout } = props;
if (!isAuthenticated) {
return (
@ -31,6 +31,12 @@ const MobileMenu = props => {
);
}
const inboxLink = (
<NamedLink name={currentUserHasListings ? 'InboxSalesPage' : 'InboxOrdersPage'}>
<FormattedMessage id="MobileMenu.inboxLink" />
</NamedLink>
);
return (
<div className={css.root}>
<div className={css.user}>
@ -43,9 +49,7 @@ const MobileMenu = props => {
</div>
</div>
<div className={css.content}>
<NamedLink name="InboxPage" params={{ tab: 'orders' }}>
<FormattedMessage id="MobileMenu.inboxLink" />
</NamedLink>
{inboxLink}
</div>
<div className={css.footer}>
<InlineButton className={css.logoutButton} onClick={onLogout}>
@ -62,6 +66,7 @@ const { bool, func, string } = PropTypes;
MobileMenu.propTypes = {
isAuthenticated: bool.isRequired,
currentUserHasListings: bool.isRequired,
name: string,
onLogout: func.isRequired,
};

View file

@ -1,5 +1,5 @@
import { addMarketplaceEntities } from '../../ducks/marketplaceData.duck';
import { createStripeAccount } from '../../ducks/user.duck';
import { createStripeAccount, fetchCurrentUserHasListingsSuccess } from '../../ducks/user.duck';
const requestAction = actionType => params => ({ type: actionType, payload: { params } });
@ -162,6 +162,13 @@ export function requestCreateListing(data) {
dispatch(requestShowListing({ id, include: ['author', 'images'] }));
return response;
})
.then(response => {
// We must update the user duck since this might be the first
// listing for the user, therefore changing the
// currentUserHasListings flag in the store.
dispatch(fetchCurrentUserHasListingsSuccess({ hasListings: true }));
return response;
})
.catch(e => dispatch(createListingError(e)));
};
}

View file

@ -1,7 +1,6 @@
import { reverse, sortBy } from 'lodash';
import { parse } from '../../util/urlHelpers';
import { addMarketplaceEntities } from '../../ducks/marketplaceData.duck';
import { fetchCurrentUser } from '../../ducks/user.duck';
const sortedTransactions = txs =>
reverse(
@ -16,10 +15,6 @@ export const FETCH_ORDERS_OR_SALES_REQUEST = 'app/InboxPage/FETCH_ORDERS_OR_SALE
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';
export const FETCH_CURRENT_USER_HAS_LISTINGS_REQUEST = 'app/InboxPage/FETCH_CURRENT_USER_HAS_LISTINGS_REQUEST';
export const FETCH_CURRENT_USER_HAS_LISTINGS_SUCCESS = 'app/InboxPage/FETCH_CURRENT_USER_HAS_LISTINGS_SUCCESS';
export const FETCH_CURRENT_USER_HAS_LISTINGS_ERROR = 'app/InboxPage/FETCH_CURRENT_USER_HAS_LISTINGS_ERROR';
// ================ Reducer ================ //
const entityRefs = entities =>
@ -33,8 +28,6 @@ const initialState = {
fetchOrdersOrSalesError: null,
pagination: null,
transactionRefs: [],
currentUserHasListingsError: null,
currentUserHasListings: false,
};
export default function checkoutPageReducer(state = initialState, action = {}) {
@ -55,14 +48,6 @@ export default function checkoutPageReducer(state = initialState, action = {}) {
console.error(payload); // eslint-disable-line
return { ...state, fetchInProgress: false, fetchOrdersOrSalesError: payload };
case FETCH_CURRENT_USER_HAS_LISTINGS_REQUEST:
return { ...state, currentUserHasListingsError: null };
case FETCH_CURRENT_USER_HAS_LISTINGS_SUCCESS:
return { ...state, currentUserHasListings: payload.hasListings };
case FETCH_CURRENT_USER_HAS_LISTINGS_ERROR:
console.error(payload); // eslint-disable-line
return { ...state, currentUserHasListingsError: payload };
default:
return state;
}
@ -81,46 +66,8 @@ const fetchOrdersOrSalesError = e => ({
payload: e,
});
const fetchCurrentUserHasListingsRequest = () => ({
type: FETCH_CURRENT_USER_HAS_LISTINGS_REQUEST,
});
const fetchCurrentUserHasListingsSuccess = hasListings => ({
type: FETCH_CURRENT_USER_HAS_LISTINGS_SUCCESS,
payload: { hasListings },
});
const fetchCurrentUserHasListingsError = e => ({
type: FETCH_CURRENT_USER_HAS_LISTINGS_ERROR,
error: true,
payload: e,
});
// ================ Thunks ================ //
const fetchCurrentUserHasListings = () =>
(dispatch, getState, sdk) => {
dispatch(fetchCurrentUserHasListingsRequest());
dispatch(fetchCurrentUser())
.then(() => {
const currentUserId = getState().user.currentUser.id;
const params = {
author_id: currentUserId,
// Since we are only interested in if the user has
// listings, we only need at most one result.
page: 1,
per_page: 1,
};
return sdk.listings.query(params);
})
.then(response => {
const hasListings = response.data.data && response.data.data.length > 0;
dispatch(fetchCurrentUserHasListingsSuccess(hasListings));
})
.catch(e => dispatch(fetchCurrentUserHasListingsError(e)));
};
const INBOX_PAGE_SIZE = 10;
export const loadData = (params, search) =>
@ -138,7 +85,6 @@ export const loadData = (params, search) =>
}
dispatch(fetchOrdersOrSalesRequest());
dispatch(fetchCurrentUserHasListings());
const { page = 1 } = parse(search);

View file

@ -33,7 +33,6 @@ describe('InboxPage', () => {
lastTransitionedAt: new Date(Date.UTC(2016, 0, 15)),
}),
],
currentUserHasListings: true,
intl: fakeIntl,
};
@ -67,7 +66,6 @@ describe('InboxPage', () => {
lastTransitionedAt: new Date(Date.UTC(2016, 0, 15)),
}),
],
currentUserHasListings: true,
intl: fakeIntl,
};

View file

@ -28,7 +28,7 @@ export const OrderPageComponent = props => {
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' }} />;
return <NamedRedirect name="InboxOrdersPage" />;
}
const detailsProps = {

View file

@ -14,7 +14,15 @@ 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 { currentUser, fetchSaleError, intl, onAcceptSale, onRejectSale, params, transaction } = props;
const {
currentUser,
fetchSaleError,
intl,
onAcceptSale,
onRejectSale,
params,
transaction,
} = props;
const currentTransaction = ensureTransaction(transaction);
const currentListing = ensureListing(currentTransaction.listing);
const title = currentListing.attributes.title;
@ -29,7 +37,7 @@ export const SalePageComponent = props => {
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' }} />;
return <NamedRedirect name="InboxSalesPage" />;
}
const detailsProps = {

View file

@ -84,6 +84,7 @@ class TopbarComponent extends Component {
isAuthenticated,
authInProgress,
currentUser,
currentUserHasListings,
intl,
location,
togglePageClassNames,
@ -100,7 +101,12 @@ class TopbarComponent extends Component {
const isMobileMenuOpen = mobilemenu === 'open';
const isMobileSearchOpen = mobilesearch === 'open';
const mobileMenu = (
<MobileMenu isAuthenticated={isAuthenticated} name={name} onLogout={this.handleLogout} />
<MobileMenu
isAuthenticated={isAuthenticated}
currentUserHasListings={currentUserHasListings}
name={name}
onLogout={this.handleLogout}
/>
);
// Only render current search if full place object is available in the URL params
@ -161,6 +167,7 @@ TopbarComponent.propTypes = {
isAuthenticated: bool.isRequired,
authInProgress: bool.isRequired,
currentUser: propTypes.currentUser,
currentUserHasListings: bool.isRequired,
onLogout: func.isRequired,
togglePageClassNames: func.isRequired,
@ -181,11 +188,12 @@ TopbarComponent.propTypes = {
const mapStateToProps = state => {
const { isAuthenticated } = state.Auth;
const { currentUser } = state.user;
const { currentUser, currentUserHasListings } = state.user;
return {
isAuthenticated,
authInProgress: authenticationInProgress(state),
currentUser,
currentUserHasListings,
};
};

View file

@ -10,11 +10,17 @@ export const STRIPE_ACCOUNT_CREATE_REQUEST = 'app/user/STRIPE_ACCOUNT_CREATE_REQ
export const STRIPE_ACCOUNT_CREATE_SUCCESS = 'app/user/STRIPE_ACCOUNT_CREATE_SUCCESS';
export const STRIPE_ACCOUNT_CREATE_ERROR = 'app/user/STRIPE_ACCOUNT_CREATE_ERROR';
export const FETCH_CURRENT_USER_HAS_LISTINGS_REQUEST = 'app/user/FETCH_CURRENT_USER_HAS_LISTINGS_REQUEST';
export const FETCH_CURRENT_USER_HAS_LISTINGS_SUCCESS = 'app/user/FETCH_CURRENT_USER_HAS_LISTINGS_SUCCESS';
export const FETCH_CURRENT_USER_HAS_LISTINGS_ERROR = 'app/user/FETCH_CURRENT_USER_HAS_LISTINGS_ERROR';
// ================ Reducer ================ //
const initialState = {
currentUser: null,
usersMeError: null,
currentUserHasListings: false,
currentUserHasListingsError: null,
};
export default function reducer(state = initialState, action = {}) {
@ -30,7 +36,21 @@ export default function reducer(state = initialState, action = {}) {
return { ...state, usersMeError: payload };
case CLEAR_CURRENT_USER:
return { ...state, currentUser: null, usersMeError: null };
return {
...state,
currentUser: null,
usersMeError: null,
currentUserHasListings: false,
currentUserHasListingsError: null,
};
case FETCH_CURRENT_USER_HAS_LISTINGS_REQUEST:
return { ...state, currentUserHasListingsError: null };
case FETCH_CURRENT_USER_HAS_LISTINGS_SUCCESS:
return { ...state, currentUserHasListings: payload.hasListings };
case FETCH_CURRENT_USER_HAS_LISTINGS_ERROR:
console.error(payload); // eslint-disable-line
return { ...state, currentUserHasListingsError: payload };
default:
return state;
@ -67,8 +87,55 @@ export const stripeAccountCreateError = e => ({
error: true,
});
const fetchCurrentUserHasListingsRequest = () => ({
type: FETCH_CURRENT_USER_HAS_LISTINGS_REQUEST,
});
const fetchCurrentUserHasListingsSuccess = hasListings => ({
type: FETCH_CURRENT_USER_HAS_LISTINGS_SUCCESS,
payload: { hasListings },
});
const fetchCurrentUserHasListingsError = e => ({
type: FETCH_CURRENT_USER_HAS_LISTINGS_ERROR,
error: true,
payload: e,
});
// ================ Thunks ================ //
export const fetchCurrentUserHasListings = () =>
(dispatch, getState, sdk) => {
dispatch(fetchCurrentUserHasListingsRequest());
const { currentUser } = getState().user;
if (!currentUser) {
dispatch(fetchCurrentUserHasListingsSuccess(false));
return Promise.resolve(null);
}
const currentUserId = currentUser.id;
const params = {
author_id: currentUserId,
// Since we are only interested in if the user has
// listings, we only need at most one result.
page: 1,
per_page: 1,
};
return sdk.listings
.query(params)
.then(response => {
const hasListings = response.data.data && response.data.data.length > 0;
dispatch(fetchCurrentUserHasListingsSuccess(!!hasListings));
})
.catch(e => {
// TODO: dispatch flash message
dispatch(fetchCurrentUserHasListingsError(e));
});
};
export const fetchCurrentUser = () =>
(dispatch, getState, sdk) => {
dispatch(usersMeRequest());
@ -84,6 +151,9 @@ export const fetchCurrentUser = () =>
.then(response => {
dispatch(usersMeSuccess(response.data.data));
})
.then(() => {
dispatch(fetchCurrentUserHasListings());
})
.catch(e => {
// TODO: dispatch flash message
dispatch(usersMeError(e));

View file

@ -158,14 +158,22 @@ const routesConfiguration = [
auth: true,
exact: true,
name: 'InboxBasePage',
component: () => <NamedRedirect name="InboxPage" params={{ tab: 'orders' }} />,
component: () => <NamedRedirect name="InboxSalesPage" />,
},
{
path: '/inbox/:tab',
path: '/inbox/sales',
auth: true,
exact: true,
name: 'InboxPage',
component: props => <InboxPage {...props} />,
name: 'InboxSalesPage',
component: props => <InboxPage {...props} tab="sales" />,
loadData: (params, search) => InboxPage.loadData(params, search),
},
{
path: '/inbox/orders',
auth: true,
exact: true,
name: 'InboxOrdersPage',
component: props => <InboxPage {...props} tab="orders" />,
loadData: (params, search) => InboxPage.loadData(params, search),
},
{