Skip to content

Commit

Permalink
feat(core): fetch and show digital items in Cart (#713)
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgemoya committed Mar 28, 2024
1 parent 0ec2269 commit 643033a
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 114 deletions.
5 changes: 5 additions & 0 deletions .changeset/fair-shrimps-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": patch
---

Fetch and show digital items in Cart summary.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { getCart } from '~/client/queries/get-cart';

import { updateProductQuantity } from '../_actions/update-product-quantity';

import { Product } from './cart-item';

type CartLineItemInput = ReturnType<typeof graphql.scalar<'CartLineItemInput'>>;
type CartSelectedOptionsInput = ReturnType<typeof graphql.scalar<'CartSelectedOptionsInput'>>;
type UpdateCartLineItemInput = ReturnType<typeof graphql.scalar<'UpdateCartLineItemInput'>>;
Expand Down Expand Up @@ -124,9 +126,8 @@ const parseSelectedOptions = (selectedOptions: CartItemData['selectedOptions'])
}, {});
};

export const CartItemCounter = ({ itemData }: { itemData: CartItemData }) => {
const { quantity, lineItemEntityId, productEntityId, variantEntityId, selectedOptions } =
itemData;
export const CartItemCounter = ({ product }: { product: Product }) => {
const { quantity, entityId, productEntityId, variantEntityId, selectedOptions } = product;

const [counterValue, setCounterValue] = useState<'' | number>(quantity);
const handleCountUpdate = async (value: string | number) => {
Expand All @@ -140,7 +141,7 @@ export const CartItemCounter = ({ itemData }: { itemData: CartItemData }) => {

const productData: UpdateProductQuantityData = Object.assign(
{
lineItemEntityId,
lineItemEntityId: entityId,
productEntityId,
quantity: Number(value),
selectedOptions: parseSelectedOptions(selectedOptions),
Expand Down
119 changes: 119 additions & 0 deletions apps/core/app/[locale]/(default)/cart/_components/cart-item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Trash } from 'lucide-react';
import { getTranslations } from 'next-intl/server';

import { getCart } from '~/client/queries/get-cart';
import { ExistingResultType } from '~/client/util';
import { BcImage } from '~/components/bc-image';

import { removeProduct } from '../_actions/remove-products';

import { CartItemCounter } from './cart-item-counter';

export type Product =
| ExistingResultType<typeof getCart>['lineItems']['physicalItems'][number]
| ExistingResultType<typeof getCart>['lineItems']['digitalItems'][number];

export const CartItem = async ({
currencyCode,
product,
}: {
currencyCode: string;
product: Product;
}) => {
const t = await getTranslations('Cart');

const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currencyCode,
});

return (
<li>
<div className="flex items-center gap-6 border-t border-t-gray-200 py-4">
<div>
<BcImage alt={product.name} height={104} src={product.imageUrl ?? ''} width={104} />
</div>

<div className="flex-1">
<p className="text-base text-gray-500">{product.brand}</p>
<p className="text-xl font-bold lg:text-2xl">{product.name}</p>

{product.selectedOptions.length > 0 && (
<div className="mt-2">
{product.selectedOptions.map((selectedOption) => {
switch (selectedOption.__typename) {
case 'CartSelectedMultipleChoiceOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.value}</span>
</div>
);

case 'CartSelectedCheckboxOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.value}</span>
</div>
);

case 'CartSelectedNumberFieldOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.number}</span>
</div>
);

case 'CartSelectedMultiLineTextFieldOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.text}</span>
</div>
);

case 'CartSelectedTextFieldOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.text}</span>
</div>
);

case 'CartSelectedDateFieldOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">
{Intl.DateTimeFormat().format(new Date(selectedOption.date.utc))}
</span>
</div>
);
}

return null;
})}
</div>
)}
</div>

<CartItemCounter product={product} />

<div>
<p className="inline-flex w-24 justify-center text-lg font-bold">
{currencyFormatter.format(product.extendedSalePrice.value)}
</p>
</div>

<form action={removeProduct}>
<input name="lineItemEntityId" type="hidden" value={product.entityId} />
<button aria-label={t('removeFromCart')} type="submit">
<Trash aria-hidden="true" />
</button>
</form>
</div>
</li>
);
};
116 changes: 6 additions & 110 deletions apps/core/app/[locale]/(default)/cart/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { Button } from '@bigcommerce/components/button';
import { Trash2 as Trash } from 'lucide-react';
import { cookies } from 'next/headers';
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import { Suspense } from 'react';

import { getCheckoutUrl } from '~/client/management/get-checkout-url';
import { getCart } from '~/client/queries/get-cart';
import { BcImage } from '~/components/bc-image';
import { LocaleType } from '~/i18n';

import { removeProduct } from './_actions/remove-products';
import { CartItemCounter } from './_components/cart-item-counter';
import { CartItem } from './_components/cart-item';
import { CheckoutSummary } from './_components/checkout-summary';

export const metadata = {
Expand Down Expand Up @@ -62,118 +59,17 @@ export default async function CartPage({ params: { locale } }: Props) {
return <EmptyCart />;
}

const extractCartlineItemsData = ({
entityId,
productEntityId,
quantity,
variantEntityId,
selectedOptions,
}: (typeof cart.lineItems.physicalItems)[number]) => ({
lineItemEntityId: entityId,
productEntityId,
quantity,
variantEntityId,
selectedOptions,
});

return (
<div>
<h1 className="pb-6 text-4xl font-black lg:pb-10 lg:text-5xl">{t('heading')}</h1>
<div className="pb-12 md:grid md:grid-cols-2 md:gap-8 lg:grid-cols-3">
<ul className="col-span-2">
{cart.lineItems.physicalItems.map((product) => (
<li key={product.entityId}>
<div className="flex items-center gap-6 border-t border-t-gray-200 py-4">
<div>
<BcImage
alt={product.name}
height={104}
src={product.imageUrl ?? ''}
width={104}
/>
</div>

<div className="flex-1">
<p className="text-base text-gray-500">{product.brand}</p>
<p className="text-xl font-bold lg:text-2xl">{product.name}</p>

{product.selectedOptions.length > 0 && (
<div className="mt-2">
{product.selectedOptions.map((selectedOption) => {
switch (selectedOption.__typename) {
case 'CartSelectedMultipleChoiceOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.value}</span>
</div>
);

case 'CartSelectedCheckboxOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.value}</span>
</div>
);

case 'CartSelectedNumberFieldOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.number}</span>
</div>
);

case 'CartSelectedMultiLineTextFieldOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.text}</span>
</div>
);

case 'CartSelectedTextFieldOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">{selectedOption.text}</span>
</div>
);

case 'CartSelectedDateFieldOption':
return (
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">
{Intl.DateTimeFormat().format(new Date(selectedOption.date.utc))}
</span>
</div>
);
}

return null;
})}
</div>
)}
</div>

<CartItemCounter itemData={extractCartlineItemsData(product)} />

<div>
<p className="inline-flex w-24 justify-center text-lg font-bold">
${product.extendedSalePrice.value}
</p>
</div>

<form action={removeProduct}>
<input name="lineItemEntityId" type="hidden" value={product.entityId} />
<button aria-label={t('removeFromCart')} type="submit">
<Trash aria-hidden="true" />
</button>
</form>
</div>
</li>
<CartItem currencyCode={cart.currencyCode} key={product.entityId} product={product} />
))}

{cart.lineItems.digitalItems.map((product) => (
<CartItem currencyCode={cart.currencyCode} key={product.entityId} product={product} />
))}
</ul>

Expand Down
42 changes: 42 additions & 0 deletions apps/core/client/queries/get-cart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,48 @@ const GET_CART_QUERY = graphql(
}
}
}
digitalItems {
name
brand
imageUrl
entityId
quantity
productEntityId
variantEntityId
extendedListPrice {
...MoneyFields
}
extendedSalePrice {
...MoneyFields
}
selectedOptions {
__typename
entityId
name
... on CartSelectedMultipleChoiceOption {
value
valueEntityId
}
... on CartSelectedCheckboxOption {
value
valueEntityId
}
... on CartSelectedNumberFieldOption {
number
}
... on CartSelectedMultiLineTextFieldOption {
text
}
... on CartSelectedTextFieldOption {
text
}
... on CartSelectedDateFieldOption {
date {
utc
}
}
}
}
}
amount {
...MoneyFields
Expand Down

0 comments on commit 643033a

Please sign in to comment.