ManageListingPage with ManageListingCards and loadData

This commit is contained in:
Vesa Luusua 2017-08-28 14:41:56 +03:00
parent c8e6c9901b
commit 3ff50f9dc1
6 changed files with 220 additions and 3 deletions

View file

@ -33,3 +33,111 @@
margin-left: 24px;
}
}
.listingPanel {
width: 100%;
margin: 24px auto 0 auto;
@media (--viewportMedium) {
margin: 48px auto 0 auto;
}
@media (--viewportLarge) {
margin: 80px auto 0 auto;
max-width: 62.5vw;
}
}
.title {
margin: 0 24px 24px 24px;
@media (--viewportMedium) {
margin: 0 24px 48px 24px;
}
@media (--viewportLarge) {
margin: 0 36px 48px 36px;
}
}
.listingCards {
padding: 0 24px 96px 24px;
@media (--viewportMedium) {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
@media (--viewportLarge) {
padding: 0 36px 36px 36px;
}
}
.listingCard {
margin-bottom: 36px;
flex-basis: 100%;
@media (--viewportMedium) {
/**
* resultPanel: 62.5vw from 1024px = 640px
* panelPaddings: - 2*36px = 72px
*
* columnCount: 2
* gutterWidthBetweenColumns: 24px
*
* (resultPanel - (panelPaddings + gutterWidthBetweenColumns)) / columnCount = listingCardWidth
* ergo => listingCardWidth: 272px
*
* flex-basis: calc((100%/columnCount) - (guttersBetweenColumns / columnCount))
*/
flex-basis: calc(50% - 12px);
margin-right: 24px;
}
@media (--viewportXLarge) {
/**
* resultPanelWidthRatio = 0.625 aka 62.5%
*
* resultPanel: 62.5vw from 1920px = 1200px
* panelPaddings: - 2*36px = 72px
*
* columnCount: 3
* guttersBetweenColumns: 2*24px = 48px
*
* (resultPanel - (panelPaddings + guttersBetweenColumns)) / columnCount = listingCardWidth
* ergo => listingCardWidth: 360px
*
* flex-basis: calc((100%/columnCount) - (guttersBetweenColumns / columnCount))
*/
flex-basis: calc(33.33% - 16px);
margin-right: 24px;
}
}
/**
* Remove margin-right from listingCards on the last column
* This is a special case in the world of breakpoints. Nth-of-type effects every nth item.
* Here we take margin-right away, but we don't want it affect the next column breakpoint.
*/
.listingCard:nth-of-type(2n) {
@media screen and (min-width: 768px) and (max-width: 1920px) {
margin-right: 0;
}
}
.listingCard:nth-of-type(3n) {
@media (--viewportXLarge) {
margin-right: 0;
}
}
.pagination {
/* margin-top: auto; pushes pagination to the end of the page. */
margin: auto 24px 0 24px;
@media (--viewportLarge) {
margin: auto 36px 0 36px;
}
}

View file

@ -1,11 +1,21 @@
import React, { PropTypes } from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { withRouter } from 'react-router-dom';
import * as propTypes from '../../util/propTypes';
import { parse } from '../../util/urlHelpers';
import { logout, authenticationInProgress } from '../../ducks/Auth.duck';
import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck';
import { PageLayout, Topbar, UserNav } from '../../components';
import { ManageListingCard, PageLayout, PaginationLinks, Topbar, UserNav } from '../../components';
import { getListingsById, queryOwnListings } from './ManageListingsPage.duck';
import css from './ManageListingsPage.css';
// Pagination page size might need to be dynamic on responsive page layouts
// Current design has max 3 columns 96 is divisible by 2 and 3
// So, there's enough cards to fill all columns on full pagination pages
const RESULT_PAGE_SIZE = 96;
export const ManageListingsPageComponent = props => {
const {
@ -15,13 +25,57 @@ export const ManageListingsPageComponent = props => {
currentUserHasListings,
history,
isAuthenticated,
listings,
location,
logoutError,
notificationCount,
onLogout,
onManageDisableScrolling,
pagination,
queryInProgress,
queryListingsError,
queryParams,
} = props;
const hasPaginationInfo = !!pagination && pagination.totalItems != null;
const listingsAreLoaded = !queryInProgress && hasPaginationInfo;
const loadingResults = (
<h2>
<FormattedMessage id="ManageListingsPage.loadingOwnListings" />
</h2>
);
const noResults = (
<h2>
<FormattedMessage id="ManageListingsPage.noResults" />
</h2>
);
const queryError = (
<h2 className={css.error}>
<FormattedMessage id="ManageListingsPage.queryError" />
</h2>
);
const title = listingsAreLoaded
? <h1 className={css.title}>
<FormattedMessage
id="ManageListingsPage.youHaveListings"
values={{ count: pagination.totalItems }}
/>
</h1>
: null;
const page = queryParams ? queryParams.page : 1;
const paginationLinks = listingsAreLoaded && pagination && pagination.totalPages > 1
? <PaginationLinks
className={css.pagination}
pageName="ManageListingsPage"
pageSearchParams={{ page }}
pagination={pagination}
/>
: null;
return (
<PageLayout authInfoError={authInfoError} logoutError={logoutError} title="Manage listings">
<Topbar
@ -37,6 +91,19 @@ export const ManageListingsPageComponent = props => {
onManageDisableScrolling={onManageDisableScrolling}
/>
<UserNav selectedPageName="ManageListingsPage" />
{queryInProgress ? loadingResults : null}
{queryListingsError ? queryError : null}
{listingsAreLoaded && pagination.totalItems === 0 ? noResults : null}
<div className={css.listingPanel}>
{title}
<div className={css.listingCards}>
{listings.map(l => (
<ManageListingCard className={css.listingCard} key={l.id.uuid} listing={l} />
))}
</div>
{paginationLinks}
</div>
</PageLayout>
);
};
@ -44,11 +111,15 @@ export const ManageListingsPageComponent = props => {
ManageListingsPageComponent.defaultProps = {
authInfoError: null,
currentUser: null,
listings: [],
logoutError: null,
notificationCount: 0,
pagination: null,
queryListingsError: null,
queryParams: null,
};
const { bool, func, instanceOf, number, object, shape } = PropTypes;
const { arrayOf, bool, func, instanceOf, number, object, shape } = PropTypes;
ManageListingsPageComponent.propTypes = {
authInfoError: instanceOf(Error),
@ -56,10 +127,15 @@ ManageListingsPageComponent.propTypes = {
currentUser: propTypes.currentUser,
currentUserHasListings: bool.isRequired,
isAuthenticated: bool.isRequired,
listings: arrayOf(propTypes.ownListing),
logoutError: instanceOf(Error),
notificationCount: number,
onLogout: func.isRequired,
onManageDisableScrolling: func.isRequired,
pagination: propTypes.pagination,
queryInProgress: bool.isRequired,
queryListingsError: instanceOf(Error),
queryParams: object,
// from withRouter
history: shape({
@ -69,6 +145,14 @@ ManageListingsPageComponent.propTypes = {
};
const mapStateToProps = state => {
const {
currentPageResultIds,
pagination,
queryInProgress,
queryListingsError,
queryParams,
} = state.ManageListingsPage;
const listings = getListingsById(state, currentPageResultIds);
// PageLayout needs authInfoError and logoutError, Topbar needs isAuthenticated
const { authInfoError, isAuthenticated, logoutError } = state.Auth;
// Topbar needs user info.
@ -80,11 +164,17 @@ const mapStateToProps = state => {
return {
authInfoError,
authInProgress: authenticationInProgress(state),
currentPageResultIds,
currentUser,
currentUserHasListings,
currentUserNotificationCount: notificationCount,
isAuthenticated,
listings,
logoutError,
pagination,
queryInProgress,
queryListingsError,
queryParams,
scrollingDisabled: isScrollingDisabled(state),
};
};
@ -99,4 +189,15 @@ const ManageListingsPage = compose(connect(mapStateToProps, mapDispatchToProps),
ManageListingsPageComponent
);
ManageListingsPage.loadData = (params, search) => {
const queryParams = parse(search);
const page = queryParams.page || 1;
return queryOwnListings({
...queryParams,
page,
perPage: RESULT_PAGE_SIZE,
include: ['images'],
});
};
export default ManageListingsPage;

View file

@ -13,6 +13,7 @@ describe('ContactDetailsPage', () => {
location={{ search: '' }}
scrollingDisabled={false}
authInProgress={false}
queryInProgress={false}
currentUserHasListings={false}
isAuthenticated={false}
onLogout={noop}

View file

@ -26,5 +26,8 @@ exports[`ContactDetailsPage matches snapshot 1`] = `
className={null}
rootClassName={null}
selectedPageName="ManageListingsPage" />
<div>
<div />
</div>
</withRouter(PageLayout)>
`;

View file

@ -235,6 +235,7 @@ const routesConfiguration = [
exact: true,
name: 'ManageListingsPage',
component: props => <ManageListingsPage {...props} />,
loadData: (params, search) => ManageListingsPage.loadData(params, search),
},
{
path: '/account',

View file

@ -159,7 +159,11 @@
"ManageListingCard.unsupportedPrice": "({currency})",
"ManageListingCard.unsupportedPriceTitle": "Unsupported currency ({currency})",
"ManageListingsPage.accountSettings": "Account settings",
"ManageListingsPage.loadingOwnListings": "Loading listings…",
"ManageListingsPage.noResults": "You don't have any listings.",
"ManageListingsPage.profileSettings": "Profile settings",
"ManageListingsPage.queryError": "Query failed. Please try again.",
"ManageListingsPage.youHaveListings": "You have {count} listings",
"ManageListingsPage.yourListings": "Your listings",
"MapPriceMarker.unsupportedPrice": "({currency})",
"Modal.close": "CLOSE",
@ -257,7 +261,6 @@
"SearchPage.foundResults": "{count, number} {count, plural, one {sauna} other {saunas}} found",
"SearchPage.foundResultsWithAddress": "{count, number} {count, plural, one {sauna} other {saunas}} around {address}",
"SearchPage.loadingResults": "Loading search results…",
"SearchPage.loadingResults": "Loading search results…",
"SearchPage.noResults": "Could not find any listings.",
"SearchPage.openMapView": "Map view",
"SearchPage.searchError": "Search failed. Please try again.",