diff --git a/src/components/SaleDetailsPanel/SaleDetailsPanel.css b/src/components/SaleDetailsPanel/SaleDetailsPanel.css index dacd8518..d8b9c7ab 100644 --- a/src/components/SaleDetailsPanel/SaleDetailsPanel.css +++ b/src/components/SaleDetailsPanel/SaleDetailsPanel.css @@ -1,53 +1,166 @@ @import '../../marketplace.css'; .root { - padding: 0 24px; + +} + +.container { + display: flex; + flex-direction: column; + + @media (--viewportMedium) { + flex-direction: row; + justify-content: center; + } } .nowrap { white-space: nowrap; } -.header { - display: flex; - margin-top: 28px; - margin-bottom: 24px; +.aspectWrapperMobile, +.aspectWrapperDesktop { + position: relative; + padding-bottom: 66.6667%; /* 3:2 Aspect Ratio */ + background-color: var(--matterColorNegative); /* Loading BG color */ +} + +.aspectWrapperMobile { + @media (--viewportMedium) { + display: none; + } +} + +.aspectWrapperDesktop { + display: none; + + @media (--viewportMedium) { + display: block; + } +} + +.rootForImage { + /* Layout - image will take space defined by aspect ratio wrapper */ + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; +} + +.info { + @media (--viewportMedium) { + margin-top: 100px; + max-width: 517px; + } } .title { - flex: 1; - margin: 0 12px 0 0; + margin: 22px 24px 0 24px; @media (--viewportMedium) { - margin: 0 12px 0 0; + margin: 16px 24px 0 24px; } } .message { - margin: 0 0 47px 0; + margin: 24px 24px 0 24px; + + @media (--viewportMedium) { + margin: 23px 24px 0 24px; + } } -.avatarWrapper { - width: auto; - margin-top: 7px; +.avatarWrapperMobile { + /* Position (over the listing image)*/ + margin-top: -31px; + + /* Bring on top of image */ + position: relative; + + /* Layout */ + display: flex; + justify-content: center; + width: 100%; + + @media (--viewportMedium) { + display: none; + } } -.avatar span { - margin-bottom: 5px; +.avatarWrapperDesktop { + display: none; + + @media (--viewportMedium) { + display: block; + margin-left: 24px; + } } -.bookingBreakdownContainer { - margin-bottom: 40px; +.breakdownContainerMobile { + margin: 0 24px 122px 24px; + + @media (--viewportMedium) { + display: none; + } } -.bookingBreakdownTitle { +.breakdownContainerDesktop { + display: none; + + margin: 119px 24px 24px 24px; + box-shadow: var(--boxShadowBreakdown); + + @media (--viewportMedium) { + display: block; + } +} + +.breakdownTitleMobile { /* Font */ color: var(--matterColorAnti); - margin-top: 5px; - margin-bottom: 14px; + margin: 47px 0 14px 0; +} + +.breakdownTitleDesktop { + margin: 48px 48px 50px 48px; + color: var(--matterColorAnti); +} + +.breakdownDesktop { + margin: 48px; +} + +.actionButtons { + position: fixed; + bottom: 0; + width: 100%; + padding: 19px 24px 18px 24px; + + display: flex; + + /* Contain repainting to this component only */ + /* 3D painting container helps scrolling */ + transform: translate3d(0,0,0); + + box-shadow: var(--boxShadowTop); + background-color: white; + @media (--viewportMedium) { - margin-top: 5px; - margin-bottom: 14px; + position: static; + box-shadow: none; + width: auto; + margin: 100px 24px 0 24px; + padding: 0; } } + +.rejectButton { + margin-right: 6.5px; +} + +.acceptButton { + margin-left: 6.5px; +} diff --git a/src/components/SaleDetailsPanel/SaleDetailsPanel.js b/src/components/SaleDetailsPanel/SaleDetailsPanel.js index e5045a13..c7faeda4 100644 --- a/src/components/SaleDetailsPanel/SaleDetailsPanel.js +++ b/src/components/SaleDetailsPanel/SaleDetailsPanel.js @@ -4,7 +4,14 @@ import classNames from 'classnames'; import * as propTypes from '../../util/propTypes'; import { createSlug } from '../../util/urlHelpers'; import { ensureListing, ensureTransaction, ensureBooking, ensureUser } from '../../util/data'; -import { AvatarMedium, BookingBreakdown, NamedLink } from '../../components'; +import { + AvatarMedium, + BookingBreakdown, + NamedLink, + ResponsiveImage, + PrimaryButton, + SecondaryButton, +} from '../../components'; import css from './SaleDetailsPanel.css'; @@ -23,7 +30,6 @@ const breakdown = (transaction, totalLabelMessage) => { return ( { rootClassName, className, transaction, + onAcceptSale, + onRejectSale, + acceptOrRejectInProgress, } = props; const currentTransaction = ensureTransaction(transaction); const currentListing = ensureListing(currentTransaction.listing); @@ -149,24 +158,86 @@ const SaleDetailsPanel = props => { lastTransition ); + const listingTitle = currentListing.attributes.title; + const firstImage = currentListing.images && currentListing.images.length > 0 + ? currentListing.images[0] + : null; + + const isPreauthorizedState = currentTransaction.attributes.state === + propTypes.TX_STATE_PREAUTHORIZED; + const actionButtons = isPreauthorizedState + ?
+ onRejectSale(currentTransaction.id)} + > + + + onAcceptSale(currentTransaction.id)} + > + + +
+ : null; + const classes = classNames(rootClassName || css.root, className); return (
-
-

- {title} -

-
+
+
+ +
+
-
-

{message}

-
-

- -

- {bookingInfo} +
+
+ +
+

{title}

+

{message}

+ {actionButtons} +
+
+

+ +

+ {bookingInfo} +
+
+
+ +
+

+ +

+
+ {bookingInfo} +
+
); @@ -178,12 +249,15 @@ SaleDetailsPanel.defaultProps = { lastTransition: null, }; -const { string } = PropTypes; +const { string, func, bool } = PropTypes; SaleDetailsPanel.propTypes = { rootClassName: string, className: string, transaction: propTypes.transaction.isRequired, + onAcceptSale: func.isRequired, + onRejectSale: func.isRequired, + acceptOrRejectInProgress: bool, }; export default SaleDetailsPanel; diff --git a/src/components/SaleDetailsPanel/__snapshots__/SaleDetailsPanel.test.js.snap b/src/components/SaleDetailsPanel/__snapshots__/SaleDetailsPanel.test.js.snap index c5e7c780..c7233eb0 100644 --- a/src/components/SaleDetailsPanel/__snapshots__/SaleDetailsPanel.test.js.snap +++ b/src/components/SaleDetailsPanel/__snapshots__/SaleDetailsPanel.test.js.snap @@ -2,94 +2,212 @@ exports[`SaleDetailsPanel matches snapshot 1`] = `
-

- - listing1 title - , - } - } /> -

+
+ +
-
-

- +

+ +
+

+ + listing1 title + , + } + } /> +

+

+ +

+
+ + + + + + +
+
+
+

+ +

+ -

-
-

- -

- + } + userRole="provider" /> +
+
+
+ +
+

+ +

+
+ - } - userRole="provider" /> + } + } + totalLabelMessage={ + + } + userRole="provider" /> +
+
`; diff --git a/src/containers/SalePage/SalePage.css b/src/containers/SalePage/SalePage.css index c7ea2ef6..d1c0bc0a 100644 --- a/src/containers/SalePage/SalePage.css +++ b/src/containers/SalePage/SalePage.css @@ -17,21 +17,6 @@ color: var(--failColor); } -.actionButtons { - display: flex; - justify-content: space-between; - margin: auto 24px 96px 24px; - padding-top: 24px; -} - -.rejectButton { - margin-right: 6.5px; -} - -.acceptButton { - margin-left: 6.5px; -} - .tabContent { display: none; } diff --git a/src/containers/SalePage/SalePage.js b/src/containers/SalePage/SalePage.js index b8a1ed44..2d11a25f 100644 --- a/src/containers/SalePage/SalePage.js +++ b/src/containers/SalePage/SalePage.js @@ -6,14 +6,7 @@ import classNames from 'classnames'; import { FormattedMessage, intlShape, injectIntl } from 'react-intl'; import * as propTypes from '../../util/propTypes'; import { ensureListing, ensureTransaction } from '../../util/data'; -import { - PrimaryButton, - SecondaryButton, - NamedRedirect, - SaleDetailsPanel, - PageLayout, - Topbar, -} from '../../components'; +import { NamedRedirect, SaleDetailsPanel, PageLayout, Topbar } from '../../components'; import { getMarketplaceEntities } from '../../ducks/marketplaceData.duck'; import { logout, authenticationInProgress } from '../../ducks/Auth.duck'; import { manageDisableScrolling, isScrollingDisabled } from '../../ducks/UI.duck'; @@ -30,6 +23,9 @@ export const SalePageComponent = props => { currentUser, currentUserHasListings, fetchSaleError, + acceptSaleError, + rejectSaleError, + acceptOrRejectInProgress, history, intl, isAuthenticated, @@ -68,28 +64,22 @@ export const SalePageComponent = props => { ?

:

; - const panel = isDataAvailable && currentTransaction.id - ? - : loadingOrFailedFetching; - - const isPreauthorizedState = currentTransaction.attributes.state === - propTypes.TX_STATE_PREAUTHORIZED; - const actionButtons = isDataAvailable && isPreauthorizedState - ?
- onRejectSale(currentTransaction.id)} - > - - - onAcceptSale(currentTransaction.id)} - > - - -
+ const acceptError = acceptSaleError + ?

: null; + const rejectError = rejectSaleError + ?

+ : null; + + const panel = isDataAvailable && currentTransaction.id + ? + : loadingOrFailedFetching; return ( { onLogout={onLogout} onManageDisableScrolling={onManageDisableScrolling} /> + {acceptError} + {rejectError}
{panel} - {actionButtons}
); @@ -121,6 +112,8 @@ SalePageComponent.defaultProps = { authInfoError: null, currentUser: null, fetchSaleError: null, + acceptSaleError: null, + rejectSaleError: null, logoutError: null, notificationCount: 0, transaction: null, @@ -134,6 +127,9 @@ SalePageComponent.propTypes = { currentUser: propTypes.currentUser, currentUserHasListings: bool.isRequired, fetchSaleError: instanceOf(Error), + acceptSaleError: instanceOf(Error), + rejectSaleError: instanceOf(Error), + acceptOrRejectInProgress: bool.isRequired, intl: intlShape.isRequired, isAuthenticated: bool.isRequired, logoutError: instanceOf(Error), @@ -157,7 +153,13 @@ SalePageComponent.propTypes = { }; const mapStateToProps = state => { - const { fetchSaleError, transactionRef } = state.SalePage; + const { + fetchSaleError, + acceptSaleError, + rejectSaleError, + acceptOrRejectInProgress, + transactionRef, + } = state.SalePage; const { authInfoError, isAuthenticated, logoutError } = state.Auth; const { currentUser, @@ -174,6 +176,9 @@ const mapStateToProps = state => { currentUser, currentUserHasListings, fetchSaleError, + acceptSaleError, + rejectSaleError, + acceptOrRejectInProgress, isAuthenticated, logoutError, notificationCount, diff --git a/src/containers/SalePage/__snapshots__/SalePage.test.js.snap b/src/containers/SalePage/__snapshots__/SalePage.test.js.snap index ad971538..181ba829 100644 --- a/src/containers/SalePage/__snapshots__/SalePage.test.js.snap +++ b/src/containers/SalePage/__snapshots__/SalePage.test.js.snap @@ -41,6 +41,8 @@ exports[`SalePage matches snapshot 1`] = ` -
- - - - - - -
`; diff --git a/src/translations/en.json b/src/translations/en.json index f3b1170d..daf04e26 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1,6 +1,6 @@ { - "AddImages.upload": "Uploading", "AddImages.couldNotReadFile": "Could not read file", + "AddImages.upload": "Uploading", "AuthenticationPage.emailAlreadyInUse": "An account already exists with this email address. Try logging in instead.", "AuthenticationPage.loginFailed": "The email and password you entered did not match our records. Please double-check and try again.", "AuthenticationPage.loginLinkText": "Login", @@ -26,9 +26,9 @@ "BookingDatesForm.requiredDate": "Oops, make sure your date is correct!", "BookingDatesForm.requiredDate": "Oops, make sure your date is correct!", "BookingDatesForm.youWontBeChargedInfo": "You won't be charged yet", + "CheckoutPage.goToLandingPage": "Go to homepage", "CheckoutPage.hostedBy": "Hosted by {name}", "CheckoutPage.initiateOrderError": "Payment request failed. Please try again.", - "CheckoutPage.goToLandingPage": "Go to homepage", "CheckoutPage.paymentInfo": "You'll only be charged if your request is accepted by the provider.", "CheckoutPage.paymentTitle": "Payment", "CheckoutPage.priceBreakdownTitle": "Booking breakdown", @@ -89,25 +89,25 @@ "InboxPage.salesTabTitle": "Hosting", "InboxPage.salesTitle": "Inbox: Hosting", "InboxPage.stateAccepted": "Accepted", + "InboxPage.stateDeclined": "Declined", "InboxPage.stateDelivered": "Delivered", "InboxPage.statePending": "Pending", "InboxPage.stateRequested": "Requested", - "InboxPage.stateDeclined": "Declined", "InboxPage.title": "Inbox", "ListingCard.hostedBy": "Hosted by {authorName}.", "ListingCard.perNight": "per night", "ListingCard.unsupportedPrice": "({currency})", "ListingCard.unsupportedPriceTitle": "Unsupported currency ({currency})", - "ListingPage.bookingTitle": "Book {title}", "ListingPage.bookingHelp": "Start by choosing your dates.", + "ListingPage.bookingTitle": "Book {title}", "ListingPage.ctaButtonMessage": "Request to book", "ListingPage.descriptionTitle": "About this sauna", "ListingPage.hostedBy": "Hosted by {name}", "ListingPage.loadingListingData": "Loading listing data", "ListingPage.locationTitle": "Location", "ListingPage.noListingData": "Could not find listing data", - "ListingPage.perNight": "per night", "ListingPage.ownListing": "This is your own listing.", + "ListingPage.perNight": "per night", "LoginForm.emailLabel": "Email", "LoginForm.emailPlaceholder": "john.doe@example.com", "LoginForm.emailRequired": "This field is required", @@ -119,6 +119,7 @@ "Modal.close": "CLOSE", "Modal.closeModal": "Close modal", "OrderDetailsPanel.bookingBreakdownTitle": "Booking breakdown", + "OrderDetailsPanel.hostedBy": "Hosted by {name}", "OrderDetailsPanel.orderAcceptedStatus": "{providerName} accepted the request on {transitionDate}.", "OrderDetailsPanel.orderAcceptedSubtitle": "You have booked {listingLink}", "OrderDetailsPanel.orderAcceptedTitle": "Woohoo {customerName}!", @@ -131,7 +132,6 @@ "OrderDetailsPanel.orderPreauthorizedTitle": "Great success, {customerName}!", "OrderDetailsPanel.orderRejectedStatus": "Unfortunately {providerName} declined the request on {transitionDate}.", "OrderDetailsPanel.orderRejectedTitle": "You requested to book {listingLink}", - "OrderDetailsPanel.hostedBy": "Hosted by {name}", "OrderPage.fetchOrderFailed": "Fetching order data failed.", "OrderPage.loadingData": "Loading order data.", "OrderPage.title": "Order details: {listingTitle}", @@ -142,35 +142,35 @@ "PaginationLinks.toPage": "Go to page {page}", "PayoutDetailsForm.addressTitle": "Address", "PayoutDetailsForm.bankDetails": "Bank details", + "PayoutDetailsForm.birthdayDatePlaceholder": "dd", "PayoutDetailsForm.birthdayLabel": "Birth date", "PayoutDetailsForm.birthdayLabelMonth": "Month", "PayoutDetailsForm.birthdayLabelYear": "Year", - "PayoutDetailsForm.birthdayDatePlaceholder": "dd", "PayoutDetailsForm.birthdayMonthPlaceholder": "mm", - "PayoutDetailsForm.birthdayYearPlaceholder": "yyyy", "PayoutDetailsForm.birthdayRequired": "Birthday is required and must be a valid date.", + "PayoutDetailsForm.birthdayYearPlaceholder": "yyyy", "PayoutDetailsForm.cityLabel": "City", "PayoutDetailsForm.cityPlaceholder": "Helsinki", "PayoutDetailsForm.cityRequired": "This field is required", "PayoutDetailsForm.countryLabel": "Country", - "PayoutDetailsForm.countryPlaceholder": "Select your country…", - "PayoutDetailsForm.countryRequired": "This field is required", - "PayoutDetailsForm.countryNames.AU": "Australia", "PayoutDetailsForm.countryNames.AT": "Austria", + "PayoutDetailsForm.countryNames.AU": "Australia", "PayoutDetailsForm.countryNames.BE": "Belgium", + "PayoutDetailsForm.countryNames.DE": "Germany", "PayoutDetailsForm.countryNames.DK": "Denmark", + "PayoutDetailsForm.countryNames.ES": "Spain", "PayoutDetailsForm.countryNames.FI": "Finland", "PayoutDetailsForm.countryNames.FR": "France", - "PayoutDetailsForm.countryNames.DE": "Germany", + "PayoutDetailsForm.countryNames.GB": "United Kingdom", "PayoutDetailsForm.countryNames.IE": "Ireland", "PayoutDetailsForm.countryNames.IT": "Italy", "PayoutDetailsForm.countryNames.LU": "Luxembourg", "PayoutDetailsForm.countryNames.NL": "Netherlands", "PayoutDetailsForm.countryNames.PT": "Portugal", - "PayoutDetailsForm.countryNames.ES": "Spain", "PayoutDetailsForm.countryNames.SE": "Sweden", - "PayoutDetailsForm.countryNames.GB": "United Kingdom", "PayoutDetailsForm.countryNames.US": "United States", + "PayoutDetailsForm.countryPlaceholder": "Select your country…", + "PayoutDetailsForm.countryRequired": "This field is required", "PayoutDetailsForm.firstNameLabel": "First name", "PayoutDetailsForm.firstNamePlaceholder": "John", "PayoutDetailsForm.firstNameRequired": "This field is required", @@ -201,9 +201,11 @@ "SaleDetailsPanel.saleRejectedStatus": "You declined the request on {formattedDate}.", "SaleDetailsPanel.saleRequestedStatus": "{customerName} is waiting for your response.", "SalePage.acceptButton": "Accept", + "SalePage.acceptSaleFailed": "Accepting sale failed.", "SalePage.fetchSaleFailed": "Fetching sale data failed.", "SalePage.loadingData": "Loading sale data.", "SalePage.rejectButton": "Decline", + "SalePage.rejectSaleFailed": "Rejecting sale failed.", "SalePage.title": "Sale details: {title}", "SearchPage.foundResults": "{count, number} {count, plural, one {sauna} other {saunas}} found", "SearchPage.foundResultsWithAddress": "{count, number} {count, plural, one {sauna} other {saunas}} around {address}",