-
-
Save SariSaar/158b3a0e4d4229515593e8f6fcdc2ecb to your computer and use it in GitHub Desktop.
Code examples for building a shopping cart in Sharetribe Web Template – Showing purchased cart listings
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const ActivityFeedComponent = props => { | |
const { | |
... | |
cartListings, | |
} = props; | |
const isCart = cartListings?.length > 0; | |
... | |
const transitionListItem = transition => { | |
... | |
if (currentUser?.id && customer?.id && provider?.id && listing?.id) { | |
... | |
const getListingTitle = listing => | |
listing.attributes.deleted | |
? intl.formatMessage({ id: 'TransactionPage.ActivityFeed.deletedListing' }) | |
: listing.attributes.title; | |
const listingTitle = isCart | |
? cartListings.map(l => getListingTitle(l)).join(', ') | |
: getListingTitle(listing); | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from 'react'; | |
import { arrayOf, node, object, string } from 'prop-types'; | |
import { FormattedMessage } from '../../util/reactIntl'; | |
import { propTypes } from '../../util/types'; | |
import { createSlug } from '../../util/urlHelpers'; | |
import { formatMoney } from '../../util/currency'; | |
import { | |
AspectRatioWrapper, | |
AvatarMedium, | |
H4, | |
H6, | |
NamedLink, | |
ResponsiveImage, | |
} from '../../components'; | |
import css from './CheckoutPage.module.css'; | |
const CartDetailsSideCard = props => { | |
const { | |
listings, | |
author, | |
layoutListingImageConfig, | |
speculateTransactionErrorMessage, | |
processName, | |
breakdown, | |
intl, | |
} = props; | |
const listing = listings[0] || {}; | |
const unitType = listing?.publicData?.unitType || 'unknown'; | |
const { aspectWidth = 1, aspectHeight = 1, variantPrefix = 'listing-card' } = | |
layoutListingImageConfig || {}; | |
const getFirstImage = listing => (listing?.images?.length > 0 ? listing.images[0] : null); | |
const getVariants = listing => { | |
const firstImage = getFirstImage(listing); | |
return firstImage | |
? Object.keys(firstImage?.attributes?.variants).filter(k => k.startsWith(variantPrefix)) | |
: []; | |
}; | |
return ( | |
<div className={css.detailsContainerDesktop}> | |
{!!breakdown ? ( | |
<div className={css.orderBreakdownHeader}> | |
<H6 as="h3" className={css.orderBreakdownTitle}> | |
<FormattedMessage id={`CheckoutPage.${processName}.orderBreakdown`} /> | |
</H6> | |
<hr className={css.totalDivider} /> | |
</div> | |
) : null} | |
{breakdown} | |
{speculateTransactionErrorMessage} | |
{listings.map(l => ( | |
<div key={l.id.uuid}> | |
<AspectRatioWrapper | |
width={aspectWidth} | |
height={aspectHeight} | |
className={css.detailsAspectWrapper} | |
> | |
<ResponsiveImage | |
rootClassName={css.rootForImage} | |
alt={l.attributes.title} | |
image={getFirstImage(l)} | |
variants={getVariants(l)} | |
/> | |
</AspectRatioWrapper> | |
<div className={css.listingDetailsWrapper}> | |
<div className={css.avatarWrapper}> | |
<AvatarMedium user={author} disableProfileLink /> | |
</div> | |
<div className={css.detailsHeadings}> | |
<H4 as="h2"> | |
<NamedLink | |
name="ListingPage" | |
params={{ id: l?.id?.uuid, slug: createSlug(l.attributes.title) }} | |
> | |
{l.attributes.title} | |
</NamedLink> | |
</H4> | |
<div className={css.priceContainer}> | |
<p className={css.price}>{formatMoney(intl, l.attributes.price)}</p> | |
<div className={css.perUnit}> | |
<FormattedMessage | |
id="CheckoutPageWithInquiryProcess.perUnit" | |
values={{ unitType }} | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
))} | |
</div> | |
); | |
}; | |
CartDetailsSideCard.defaultProps = { | |
speculateTransactionErrorMessage: null, | |
breakdown: null, | |
}; | |
CartDetailsSideCard.propTypes = { | |
listings: arrayOf(propTypes.listing).isRequired, | |
author: propTypes.user.isRequired, | |
layoutListingImageConfig: object.isRequired, | |
speculateTransactionErrorMessage: node, | |
processName: string.isRequired, | |
breakdown: node, | |
}; | |
export default CartDetailsSideCard; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import CartDetailsSideCard from './CartDetailsSideCard'; | |
... | |
export const CheckoutPageWithPayment = props => { | |
... | |
const { listing, transaction, orderData, cartListings } = pageData; | |
... | |
const detailsSideCard = cartListings ? ( | |
<CartDetailsSideCard | |
listings={cartListings} | |
author={listing?.author} | |
layoutListingImageConfig={config.layout.listingImage} | |
speculateTransactionErrorMessage={errorMessages.speculateTransactionErrorMessage} | |
isInquiryProcess={false} | |
processName={processName} | |
breakdown={breakdown} | |
intl={intl} | |
/> | |
) : ( | |
<DetailsSideCard | |
listing={listing} | |
listingTitle={listingTitle} | |
author={listing?.author} | |
firstImage={firstImage} | |
layoutListingImageConfig={config.layout.listingImage} | |
speculateTransactionErrorMessage={errorMessages.speculateTransactionErrorMessage} | |
isInquiryProcess={false} | |
processName={processName} | |
breakdown={breakdown} | |
intl={intl} | |
/> | |
); | |
... | |
return ( | |
<Page title={title} scrollingDisabled={scrollingDisabled}> | |
... | |
</div> | |
{detailsSideCard} | |
</div> | |
</Page> | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"InboxPage.cartTitle": "{listingCount} listings", |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
export const loadData = (params, search) => (dispatch, getState, sdk) => { | |
... | |
const apiQueryParams = { | |
... | |
'fields.transaction': [ | |
'protectedData', | |
], | |
... | |
}; | |
... | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
import { getCartListingIds } from '../CartPage/CartPage.duck'; | |
const getUnitLineItem = lineItems => { | |
const unitLineItems = lineItems?.filter( | |
item => LISTING_UNIT_TYPES.includes(item.code) && !item.reversal | |
); | |
return unitLineItems; | |
}; | |
... | |
export const InboxItem = props => { | |
... | |
const listingIds = getCartListingIds(tx.attributes.protectedData.cart || {}); | |
const hasMultipleListings = listingIds.length > 1; | |
const listingCount = listingIds.length; | |
const itemTitle = hasMultipleListings | |
? intl.formatMessage({ id: 'InboxPage.cartTitle' }, { listingCount }) | |
: listing?.attributes?.title; | |
... | |
const unitLineItems = getUnitLineItem(lineItems); | |
const unitLineItemsQuantity = unitLineItems.reduce((sum, item) => { | |
return sum + Number(item.quantity); | |
}, 0); | |
const quantity = hasPricingData && !isBooking ? unitLineItemsQuantity.toString() : null; | |
const showStock = stockType === STOCK_MULTIPLE_ITEMS || (quantity && unitLineItemsQuantity > 1); | |
... | |
return ( | |
... | |
<div className={css.itemUsername}>{otherUserDisplayName}</div> | |
<div className={css.itemTitle}>{itemTitle}</div> | |
<div className={css.itemDetails}> | |
... | |
); | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getCartListingIds } from '../CartPage/CartPage.duck'; | |
... | |
// ================ Action types ================ // | |
... | |
export const SET_CART_LISTING_IDS = 'app/TransactionPage/SET_CART_LISTING_IDS'; | |
// ================ Reducer ================ // | |
const initialState = { | |
... | |
cartListingIds: [], | |
}; | |
... | |
export default function transactionPageReducer(state = initialState, action = {}) { | |
const { type, payload } = action; | |
switch (type) { | |
... | |
case SET_CART_LISTING_IDS: | |
return { ...state, cartListingIds: payload }; | |
default: | |
return state; | |
} | |
} | |
... | |
// ================ Action creators ================ // | |
... | |
export const setCartListingIds = ids => ({ | |
type: SET_CART_LISTING_IDS, | |
payload: ids, | |
}); | |
// ================ Thunks ================ // | |
... | |
export const fetchTransaction = (id, txRole, config) => (dispatch, getState, sdk) => { | |
dispatch(fetchTransactionRequest()); | |
let txResponse = null; | |
return sdk.transactions | |
.show( | |
... | |
.then(response => { | |
... | |
const { cart } = transaction.attributes.protectedData; | |
const canFetchListing = listing && listing.attributes && !listing.attributes.deleted && !cart; | |
if (canFetchListing) { | |
return sdk.listings.show({ | |
id: listingId, | |
include: ['author', 'author.profileImage', 'images'], | |
...getImageVariants(config.layout.listingImage), | |
}); | |
} else if (!!cart) { | |
const listingIds = getCartListingIds(cart); | |
dispatch(setCartListingIds(listingIds.map(id => new UUID(id)))); | |
return sdk.listings.query({ | |
ids: listingIds, | |
include: ['author', 'author.profileImage', 'images'], | |
...getImageVariants(config.layout.listingImage), | |
}); | |
} else { | |
return response; | |
} | |
... | |
}); | |
}; | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getMarketplaceEntities, getListingsById } from '../../ducks/marketplaceData.duck'; | |
... | |
export const TransactionPageComponent = props => { | |
... | |
const { | |
... | |
cartListings, | |
} = props; | |
const panel = isDataAvailable ? ( | |
<TransactionPanel | |
... | |
cartListings={cartListings} | |
activityFeed={ | |
<ActivityFeed | |
... | |
cartListings={cartListings} | |
/> | |
} | |
... | |
/> | |
) : ( | |
loadingOrFailedFetching | |
); | |
... | |
}; | |
... | |
const mapStateToProps = state => { | |
const { | |
... | |
cartListingIds, | |
} = state.TransactionPage; | |
... | |
const cartListings = getListingsById(state, cartListingIds); | |
return { | |
... | |
cartListings, | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
export class TransactionPanelComponent extends Component { | |
... | |
render() { | |
const { | |
... | |
cartListings, | |
} = this.props; | |
const isCart = cartListings?.length > 0; | |
... | |
const getFirstImage = listing => (listing?.images?.length > 0 ? listing?.images[0] : null); | |
const firstImage = getFirstImage(listing); | |
... | |
const listingDetails = isCart ? ( | |
// Show breakdown and action buttons followed by all cart listings | |
<div className={css.detailCard}> | |
{stateData.showOrderPanel ? orderPanel : null} | |
<BreakdownMaybe | |
className={css.breakdownContainer} | |
orderBreakdown={orderBreakdown} | |
processName={stateData.processName} | |
/> | |
{stateData.showActionButtons ? ( | |
<div className={css.desktopActionButtons}>{actionButtons}</div> | |
) : null} | |
{cartListings.map(l => ( | |
<div key={l.id?.uuid}> | |
<DetailCardImage | |
avatarWrapperClassName={css.avatarWrapperDesktop} | |
listingTitle={l.attributes.title} | |
image={getFirstImage(l)} | |
provider={provider} | |
isCustomer={isCustomer} | |
listingImageConfig={config.layout.listingImage} | |
/> | |
<DetailCardHeadingsMaybe | |
showDetailCardHeadings={stateData.showDetailCardHeadings} | |
listingTitle={ | |
l.attributes.deleted ? ( | |
l.attributes.title | |
) : ( | |
<NamedLink | |
name="ListingPage" | |
params={{ id: l.id?.uuid, slug: createSlug(l.attributes.title) }} | |
> | |
{l.attributes.title} | |
</NamedLink> | |
) | |
} | |
showPrice | |
price={l?.attributes?.price} | |
intl={intl} | |
/> | |
</div> | |
))} | |
</div> | |
) : ( | |
// Show listing image followed by breakdown and action buttons | |
<div className={css.detailCard}> | |
<DetailCardImage | |
avatarWrapperClassName={css.avatarWrapperDesktop} | |
listingTitle={listingTitle} | |
image={firstImage} | |
provider={provider} | |
isCustomer={isCustomer} | |
listingImageConfig={config.layout.listingImage} | |
/> | |
<DetailCardHeadingsMaybe | |
showDetailCardHeadings={stateData.showDetailCardHeadings} | |
listingTitle={ | |
listingDeleted ? ( | |
listingTitle | |
) : ( | |
<NamedLink | |
name="ListingPage" | |
params={{ id: listing.id?.uuid, slug: createSlug(listingTitle) }} | |
> | |
{listingTitle} | |
</NamedLink> | |
) | |
} | |
showPrice={showPrice} | |
price={listing?.attributes?.price} | |
intl={intl} | |
/> | |
{stateData.showOrderPanel ? orderPanel : null} | |
<BreakdownMaybe | |
className={css.breakdownContainer} | |
orderBreakdown={orderBreakdown} | |
processName={stateData.processName} | |
/> | |
{stateData.showActionButtons ? ( | |
<div className={css.desktopActionButtons}>{actionButtons}</div> | |
) : null} | |
</div> | |
); | |
return ( | |
... | |
<div className={css.stickySection}> | |
{listingDetails} | |
<DiminishedActionButtonMaybe | |
showDispute={stateData.showDispute} | |
onOpenDisputeModal={onOpenDisputeModal} | |
/> | |
</div> | |
... | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment