mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 13:06:03 +10:00
ManageListingPage with ManageListingCards and loadData
This commit is contained in:
parent
c8e6c9901b
commit
3ff50f9dc1
6 changed files with 220 additions and 3 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ describe('ContactDetailsPage', () => {
|
|||
location={{ search: '' }}
|
||||
scrollingDisabled={false}
|
||||
authInProgress={false}
|
||||
queryInProgress={false}
|
||||
currentUserHasListings={false}
|
||||
isAuthenticated={false}
|
||||
onLogout={noop}
|
||||
|
|
|
|||
|
|
@ -26,5 +26,8 @@ exports[`ContactDetailsPage matches snapshot 1`] = `
|
|||
className={null}
|
||||
rootClassName={null}
|
||||
selectedPageName="ManageListingsPage" />
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
</withRouter(PageLayout)>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -235,6 +235,7 @@ const routesConfiguration = [
|
|||
exact: true,
|
||||
name: 'ManageListingsPage',
|
||||
component: props => <ManageListingsPage {...props} />,
|
||||
loadData: (params, search) => ManageListingsPage.loadData(params, search),
|
||||
},
|
||||
{
|
||||
path: '/account',
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue