126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
import {useOptimisticCart} from '@shopify/hydrogen';
|
|
import {Link} from 'react-router';
|
|
import type {CartApiQueryFragment} from 'storefrontapi.generated';
|
|
import {useAside} from '~/components/Aside';
|
|
import {CartFreeShippingBar} from '~/components/CartFreeShippingBar';
|
|
import {CartLineItem, type CartLine} from '~/components/CartLineItem';
|
|
import {CartSummary} from './CartSummary';
|
|
|
|
export type CartLayout = 'page' | 'aside';
|
|
|
|
export type CartMainProps = {
|
|
cart: CartApiQueryFragment | null;
|
|
layout: CartLayout;
|
|
};
|
|
|
|
export type LineItemChildrenMap = {[parentId: string]: CartLine[]};
|
|
/** Returns a map of all line items and their children. */
|
|
function getLineItemChildrenMap(lines: CartLine[]): LineItemChildrenMap {
|
|
const children: LineItemChildrenMap = {};
|
|
for (const line of lines) {
|
|
if ('parentRelationship' in line && line.parentRelationship?.parent) {
|
|
const parentId = line.parentRelationship.parent.id;
|
|
if (!children[parentId]) children[parentId] = [];
|
|
children[parentId].push(line);
|
|
}
|
|
if ('lineComponents' in line) {
|
|
const children = getLineItemChildrenMap(line.lineComponents);
|
|
for (const [parentId, childIds] of Object.entries(children)) {
|
|
if (!children[parentId]) children[parentId] = [];
|
|
children[parentId].push(...childIds);
|
|
}
|
|
}
|
|
}
|
|
return children;
|
|
}
|
|
/**
|
|
* The main cart component that displays the cart items and summary.
|
|
* It is used by both the /cart route and the cart aside dialog.
|
|
*/
|
|
export function CartMain({layout, cart: originalCart}: CartMainProps) {
|
|
// The useOptimisticCart hook applies pending actions to the cart
|
|
// so the user immediately sees feedback when they modify the cart.
|
|
const cart = useOptimisticCart(originalCart);
|
|
|
|
const linesCount = Boolean(cart?.lines?.nodes?.length || 0);
|
|
const withDiscount =
|
|
cart &&
|
|
Boolean(cart?.discountCodes?.filter((code) => code.applicable)?.length);
|
|
const className = [
|
|
'cart-main',
|
|
withDiscount ? 'with-discount' : '',
|
|
layout === 'aside' ? 'cart-main--abiir-aside' : '',
|
|
]
|
|
.filter(Boolean)
|
|
.join(' ');
|
|
const cartHasItems = cart?.totalQuantity ? cart.totalQuantity > 0 : false;
|
|
const childrenMap = getLineItemChildrenMap(cart?.lines?.nodes ?? []);
|
|
|
|
return (
|
|
<section
|
|
className={className}
|
|
aria-label={layout === 'page' ? 'Cart page' : 'Cart drawer'}
|
|
>
|
|
<CartEmpty hidden={linesCount} layout={layout} />
|
|
<div
|
|
className={
|
|
layout === 'aside' ? 'cart-details cart-details--abiir-aside' : 'cart-details'
|
|
}
|
|
>
|
|
{layout === 'aside' && cartHasItems ?
|
|
<CartFreeShippingBar subtotal={cart?.cost?.subtotalAmount} />
|
|
: null}
|
|
<p id="cart-lines" className="sr-only">
|
|
Line items
|
|
</p>
|
|
<div className={layout === 'aside' ? 'cart-aside-lines' : undefined}>
|
|
<ul aria-labelledby="cart-lines">
|
|
{(cart?.lines?.nodes ?? []).map((line) => {
|
|
// we do not render non-parent lines at the root of the cart
|
|
if (
|
|
'parentRelationship' in line &&
|
|
line.parentRelationship?.parent
|
|
) {
|
|
return null;
|
|
}
|
|
return (
|
|
<CartLineItem
|
|
key={line.id}
|
|
line={line}
|
|
layout={layout}
|
|
childrenMap={childrenMap}
|
|
/>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
{cartHasItems && <CartSummary cart={cart} layout={layout} />}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function CartEmpty({
|
|
hidden = false,
|
|
}: {
|
|
hidden: boolean;
|
|
layout?: CartMainProps['layout'];
|
|
}) {
|
|
const {close} = useAside();
|
|
return (
|
|
<div hidden={hidden} className="cart-empty-aside">
|
|
<p className="cart-empty-aside__copy">
|
|
Looks like you haven’t added anything yet — let’s get you started.
|
|
</p>
|
|
<Link
|
|
to="/collections/all"
|
|
onClick={close}
|
|
prefetch="viewport"
|
|
className="cart-empty-aside__link"
|
|
>
|
|
Continue shopping
|
|
</Link>
|
|
</div>
|
|
);
|
|
}
|