Skip to content

Instantly share code, notes, and snippets.

@SariSaar
Last active February 28, 2024 11:04
Show Gist options
  • Save SariSaar/158b3a0e4d4229515593e8f6fcdc2ecb to your computer and use it in GitHub Desktop.
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
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);
...
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;
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>
);
"InboxPage.cartTitle": "{listingCount} listings",
...
export const loadData = (params, search) => (dispatch, getState, sdk) => {
...
const apiQueryParams = {
...
'fields.transaction': [
'protectedData',
],
...
};
...
};
...
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}>
...
);
};
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;
}
...
});
};
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,
};
};
...
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