diff --git a/src/components/ListingCard/ListingCard.css b/src/components/ListingCard/ListingCard.css index 5f7dacd9..a8259cc1 100644 --- a/src/components/ListingCard/ListingCard.css +++ b/src/components/ListingCard/ListingCard.css @@ -1,15 +1,21 @@ -.listing { +@import '../../marketplace.css'; + +.root { + /* Layout */ display: flex; flex-direction: column; - border-radius: 4px; - margin-bottom: 24px; - text-decoration: none; + + /* Remove link's hover effect */ + &:hover { + text-decoration: none; + } } .threeToTwoWrapper { + /* Layout */ display: block; - position: relative; width: 100%; + position: relative; } /* Firefox doesn't support image aspect ratio inside flexbox */ @@ -17,8 +23,8 @@ padding-bottom: 66.6667%; /* 3:2 Aspect Ratio */ } -.noImageContainer, -.thumbnail { +.rootForImage { + /* Layout - image will take space defined by aspect ratio wrapper */ position: absolute; top: 0; bottom: 0; @@ -27,58 +33,84 @@ width: 100%; } -.noImageContainer, -.noImageWrapper { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - background-color: #eee; -} - -.noImageText { - color: #666; -} - .info { - padding: 1rem; - -} - -.mainInfo { - width: 100%; + /* Layout */ display: flex; flex-direction: row; - justify-content: space-between; - margin-bottom: 10px; -} - -.title { - font-size: 18px; + padding: 15px 0 6px 0; } .price { + /* Layout */ display: flex; - flex-direction: row; - justify-content: flex-end; + flex-direction: column; + justify-content: flex-start; flex-shrink: 0; - padding-left: 1rem; + margin-right: 18px; } .priceValue { - font-size: 18px; + /* Font */ + @apply --marketplaceH4FontStyles; + + /* Remove default margins from font */ + margin-top: 0; + margin-bottom: 4px; + + @media (--desktopViewport) { + margin-top: 0; + margin-bottom: 4px; + } } .perNight { - margin-left: 5px; - font-size: 12px; + /* Font */ + @apply --marketplaceTinyFontStyles; + color: var(--matterColor); - /* Align small price unit text with bigger font (priceValue) */ - line-height: 23px; - padding-top: 2px; + /* Remove default margins from font */ + margin-top: 0; + margin-bottom: 0; + + @media (--desktopViewport) { + margin-top: 0; + margin-bottom: 0; + } } + +.mainInfo { + display: flex; + flex-direction: column; +} + +.title { + /* Font */ + @apply --marketplaceH4FontStyles; + color: var(--matterColor); + + /* Remove default margins from font */ + margin-top: 0; + margin-bottom: 4px; + + @media (--desktopViewport) { + margin-top: 0; + margin-bottom: 4px; + } +} + + .authorInfo { - width: 100%; - font-size: 12px; + /* Font */ + @apply --marketplaceTinyFontStyles; + color: var(--matterColor); + + /* Remove default margins from font */ + margin-top: 0; + margin-bottom: 0; + + @media (--desktopViewport) { + margin-top: 0; + margin-bottom: 0; + } } diff --git a/src/components/ListingCard/ListingCard.js b/src/components/ListingCard/ListingCard.js index 872cbf5a..840c3838 100644 --- a/src/components/ListingCard/ListingCard.js +++ b/src/components/ListingCard/ListingCard.js @@ -1,10 +1,12 @@ -import React from 'react'; +import React, { PropTypes } from 'react'; import { FormattedMessage, intlShape, injectIntl } from 'react-intl'; -import { NamedLink } from '../../components'; +import classNames from 'classnames'; +import { NamedLink, ResponsiveImage } from '../../components'; import * as propTypes from '../../util/propTypes'; import { convertMoneyToNumber } from '../../util/currency'; +import { ensureListing, ensureUser } from '../../util/data'; + import css from './ListingCard.css'; -import noImageIcon from './images/noImageIcon.svg'; const priceData = (price, currencyConfig, intl) => { if (price && price.currency === currencyConfig.currency) { @@ -13,87 +15,86 @@ const priceData = (price, currencyConfig, intl) => { return { formattedPrice, priceTitle: formattedPrice }; } else if (price) { return { - formattedPrice: `(${price.currency})`, - priceTitle: `Unsupported currency (${price.currency})`, + formattedPrice: intl.formatMessage( + { id: 'ListingCard.unsupportedPrice' }, + { currency: price.currency } + ), + priceTitle: intl.formatMessage( + { id: 'ListingCard.unsupportedPriceTitle' }, + { currency: price.currency } + ), }; } return {}; }; export const ListingCardComponent = props => { - const { currencyConfig, intl, listing } = props; - const id = listing.id.uuid; - const { title = '', price } = listing.attributes || {}; + const { className, rootClassName, currencyConfig, intl, listing } = props; + const classes = classNames(rootClassName || css.root, className); + const currentListing = ensureListing(listing); + const id = currentListing.id.uuid; + const { title = '', price } = currentListing.attributes; const slug = encodeURIComponent(title.split(' ').join('-')); - const authorName = listing.author && listing.author.attributes - ? `${listing.author.attributes.profile.firstName} ${listing.author.attributes.profile.lastName}` - : ''; + const author = ensureUser(listing.author); + const authorName = `${author.attributes.profile.firstName} ${author.attributes.profile.lastName}`; + const firstImage = currentListing.images && currentListing.images.length > 0 + ? currentListing.images[0] + : null; // TODO: Currently, API can return currencies that are not supported by starter app. const { formattedPrice, priceTitle } = priceData(price, currencyConfig, intl); - const images = listing.images - ? listing.images.map(i => ({ id: i.id, sizes: i.attributes.sizes })) - : []; - const mainImage = images.length > 0 ? images[0] : null; - const imageURL = mainImage ? mainImage.sizes.find(i => i.name === 'landscape-crop').url : null; - const image2XURL = mainImage - ? mainImage.sizes.find(i => i.name === 'landscape-crop2x').url - : null; - const higherRes = image2XURL ? { srcSet: `${image2XURL} 2x` } : null; - - // TODO: svg should have own loading strategy - // Now noImageIcon is imported with default configuration (gives url) - /* eslint-disable jsx-a11y/img-redundant-alt */ - const noListingImage = ( -
-
- No image -
No image
-
-
- ); - /* eslint-enable jsx-a11y/img-redundant-alt */ - const listingImage = ( - Listing Title - ); - - const imageOrPlaceholder = imageURL ? listingImage : noListingImage; - return ( - +
- {imageOrPlaceholder} +
+
+
+ {formattedPrice} +
+
+ +
+
{title}
-
-
- {formattedPrice} -
-
- -
+
+
-
- -
); }; +ListingCardComponent.defaultProps = { + className: null, + rootClassName: null, +}; + +const { string } = PropTypes; + ListingCardComponent.propTypes = { + className: string, + rootClassName: string, currencyConfig: propTypes.currencyConfig.isRequired, intl: intlShape.isRequired, listing: propTypes.listing.isRequired, diff --git a/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap b/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap index e89d838a..2c07f5ce 100644 --- a/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap +++ b/src/components/ListingCard/__snapshots__/ListingCard.test.js.snap @@ -1,5 +1,6 @@ exports[`ListingCard matches snapshot 1`] = `
-
-
- No image -
- No image -
-
-
+
+
+
+ 55 +
+
+ +
+
listing1 title
-
- 55 -
-
- -
+
-
- -
`; diff --git a/src/components/ResponsiveImage/ResponsiveImage.js b/src/components/ResponsiveImage/ResponsiveImage.js index ade7994c..17b514d4 100644 --- a/src/components/ResponsiveImage/ResponsiveImage.js +++ b/src/components/ResponsiveImage/ResponsiveImage.js @@ -61,9 +61,9 @@ const ResponsiveImage = props => { const srcSet = nameSet .map(v => { - const url = imageSizes.find(i => i.name === v.name).url; - return `${url} ${v.size}`; - }) + const url = imageSizes.find(i => i.name === v.name).url; + return `${url} ${v.size}`; + }) .join(', '); const sizesProp = sizes ? { sizes } : {}; diff --git a/src/translations/en.json b/src/translations/en.json index fe176c58..03ffc5dc 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -68,7 +68,9 @@ "InboxPage.stateRejected": "Rejected", "InboxPage.title": "Inbox", "ListingCard.hostedBy": "Hosted by {authorName}.", - "ListingCard.perNight": "/ night", + "ListingCard.perNight": "per night", + "ListingCard.unsupportedPrice": "({currency})", + "ListingCard.unsupportedPriceTitle": "Unsupported currency ({currency})", "ListingPage.ctaButtonMessage": "Request to book", "ListingPage.loadingListingData": "Loading listing data", "ListingPage.noListingData": "Could not find listing data",