Skip to content

Commit

Permalink
refactor(core): cart page fragment colocation (#830)
Browse files Browse the repository at this point in the history
  • Loading branch information
deini committed Apr 30, 2024
1 parent 6afe52e commit 53428a9
Show file tree
Hide file tree
Showing 20 changed files with 225 additions and 220 deletions.
Original file line number Diff line number Diff line change
@@ -1,74 +1,100 @@
import { AlertCircle } from 'lucide-react';
import { NextIntlClientProvider } from 'next-intl';
import { getFormatter, getLocale, getMessages, getTranslations } from 'next-intl/server';
import { toast } from 'react-hot-toast';

import { getCheckout } from '~/client/queries/get-checkout';

import { getShippingCountries } from '../_actions/get-shipping-countries';
import { FragmentOf, graphql } from '~/client/graphql';

import { CouponCode } from './coupon-code';
import { CouponCodeFragment } from './coupon-code/fragment';
import { ShippingEstimator } from './shipping-estimator';
import { ShippingEstimatorFragment } from './shipping-estimator/fragment';
import { getShippingCountries } from './shipping-estimator/get-shipping-countries';

const MoneyFieldsFragment = graphql(`
fragment MoneyFields on Money {
currencyCode
value
}
`);

export const CheckoutSummaryFragment = graphql(
`
fragment CheckoutSummaryFragment on Checkout {
...ShippingEstimatorFragment
...CouponCodeFragment
subtotal {
...MoneyFields
}
grandTotal {
...MoneyFields
}
taxTotal {
...MoneyFields
}
cart {
currencyCode
discountedAmount {
...MoneyFields
}
}
}
`,
[MoneyFieldsFragment, ShippingEstimatorFragment, CouponCodeFragment],
);

export const CheckoutSummary = async ({ cartId }: { cartId: string }) => {
interface Props {
data: FragmentOf<typeof CheckoutSummaryFragment>;
}

export const CheckoutSummary = async ({ data }: Props) => {
const locale = await getLocale();
const t = await getTranslations({ locale, namespace: 'Cart.CheckoutSummary' });
const format = await getFormatter({ locale });
const messages = await getMessages({ locale });

const [checkout, shippingCountries] = await Promise.all([
getCheckout(cartId),
getShippingCountries(),
]);

if (!checkout) {
toast.error(t('errorMessage'), {
icon: <AlertCircle className="text-error-secondary" />,
});
const shippingCountries = await getShippingCountries();

return null;
}
const { cart, grandTotal, subtotal, taxTotal } = data;

return (
<>
<div className="flex justify-between border-t border-t-gray-200 py-4">
<span className="font-semibold">{t('subTotal')}</span>
<span>
{format.number(checkout.subtotal?.value || 0, {
{format.number(subtotal?.value || 0, {
style: 'currency',
currency: checkout.cart?.currencyCode,
currency: cart?.currencyCode,
})}
</span>
</div>

<NextIntlClientProvider locale={locale} messages={{ Cart: messages.Cart ?? {} }}>
<ShippingEstimator checkout={checkout} shippingCountries={shippingCountries} />
<ShippingEstimator checkout={data} shippingCountries={shippingCountries} />
</NextIntlClientProvider>

{checkout.cart?.discountedAmount && (
{cart?.discountedAmount && (
<div className="flex justify-between border-t border-t-gray-200 py-4">
<span className="font-semibold">{t('discounts')}</span>
<span>
-
{format.number(checkout.cart.discountedAmount.value, {
{format.number(cart.discountedAmount.value, {
style: 'currency',
currency: checkout.cart.currencyCode,
currency: cart.currencyCode,
})}
</span>
</div>
)}

<NextIntlClientProvider locale={locale} messages={{ Cart: messages.Cart ?? {} }}>
<CouponCode checkout={checkout} />
<CouponCode checkout={data} />
</NextIntlClientProvider>

{checkout.taxTotal && (
{taxTotal && (
<div className="flex justify-between border-t border-t-gray-200 py-4">
<span className="font-semibold">{t('tax')}</span>
<span>
{format.number(checkout.taxTotal.value, {
{format.number(taxTotal.value, {
style: 'currency',
currency: checkout.cart?.currencyCode,
currency: cart?.currencyCode,
})}
</span>
</div>
Expand All @@ -77,9 +103,9 @@ export const CheckoutSummary = async ({ cartId }: { cartId: string }) => {
<div className="flex justify-between border-t border-t-gray-200 py-4 text-xl font-bold lg:text-2xl">
{t('grandTotal')}
<span>
{format.number(checkout.grandTotal?.value || 0, {
{format.number(grandTotal?.value || 0, {
style: 'currency',
currency: checkout.cart?.currencyCode,
currency: cart?.currencyCode,
})}
</span>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { graphql } from '~/client/graphql';

export const CouponCodeFragment = graphql(`
fragment CouponCodeFragment on Checkout {
entityId
coupons {
code
discountedAmount {
value
}
}
cart {
currencyCode
}
}
`);
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import { useEffect, useState } from 'react';
import { useFormStatus } from 'react-dom';
import { toast } from 'react-hot-toast';

import { getCheckout } from '~/client/queries/get-checkout';
import { ExistingResultType } from '~/client/util';
import { FragmentOf } from '~/client/graphql';

import { applyCouponCode } from '../_actions/apply-coupon-code';
import { removeCouponCode } from '../_actions/remove-coupon-code';

type Checkout = ExistingResultType<typeof getCheckout>;
import { applyCouponCode } from './apply-coupon-code';
import { CouponCodeFragment } from './fragment';
import { removeCouponCode } from './remove-coupon-code';

const SubmitButton = () => {
const t = useTranslations('Cart.SubmitCouponCode');
Expand All @@ -35,14 +33,16 @@ const SubmitButton = () => {
);
};

export const CouponCode = ({ checkout }: { checkout: ExistingResultType<typeof getCheckout> }) => {
interface Props {
checkout: FragmentOf<typeof CouponCodeFragment>;
}

export const CouponCode = ({ checkout }: Props) => {
const t = useTranslations('Cart.CheckoutSummary');
const format = useFormatter();

const [showAddCoupon, setShowAddCoupon] = useState(false);
const [selectedCoupon, setSelectedCoupon] = useState<Checkout['coupons'][number] | null>(
checkout.coupons.at(0) || null,
);
const [selectedCoupon, setSelectedCoupon] = useState(checkout.coupons.at(0) || null);

useEffect(() => {
if (checkout.coupons[0]) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { toast } from 'react-hot-toast';

import { graphql } from '~/client/graphql';

import { updateItemQuantity } from '../_actions/update-item-quantity';
import { Product } from '../cart-item';

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

type CartSelectedOptionsInput = ReturnType<typeof graphql.scalar<'CartSelectedOptionsInput'>>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { cookies } from 'next/headers';
import { graphql } from '~/client/graphql';
import { updateCartLineItem } from '~/client/mutations/update-cart-line-item';

import { removeItem } from './remove-item';
import { removeItem } from '../../_actions/remove-item';

type CartLineItemInput = ReturnType<typeof graphql.scalar<'CartLineItemInput'>>;
type UpdateCartLineItemInput = ReturnType<typeof graphql.scalar<'UpdateCartLineItemInput'>>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { graphql } from '~/client/graphql';

import { ShippingInfoFragment } from '../shipping-info/fragment';
import { ShippingOptionsFragment } from '../shipping-options/fragment';

export const ShippingEstimatorFragment = graphql(
`
fragment ShippingEstimatorFragment on Checkout {
...ShippingInfoFragment
entityId
shippingConsignments {
...ShippingOptionsFragment
selectedShippingOption {
entityId
description
}
}
handlingCostTotal {
value
}
shippingCostTotal {
currencyCode
value
}
cart {
currencyCode
}
}
`,
[ShippingOptionsFragment, ShippingInfoFragment],
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { getCountries } from '~/client/management/get-countries';
import { getShippingZones } from '~/client/management/get-shipping-zones';

export const getShippingCountries = async () => {
const shippingZones = await getShippingZones();
const [shippingZones, allCountries] = await Promise.all([getShippingZones(), getCountries()]);

const uniqueCountryZones = shippingZones.reduce<string[]>((zones, item) => {
item.locations.forEach(({ country_iso2 }) => {
if (zones.length === 0) {
Expand All @@ -23,7 +24,6 @@ export const getShippingCountries = async () => {
return zones;
}, []);

const allCountries = await getCountries();
const shippingCountries = allCountries.flatMap((countryDetails) => {
const isCountryInTheList = uniqueCountryZones.includes(countryDetails.country_iso2);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ import { Button } from '@bigcommerce/components/button';
import { useFormatter, useTranslations } from 'next-intl';
import { useEffect, useRef, useState } from 'react';

import { getCheckout } from '~/client/queries/get-checkout';
import { FragmentOf } from '~/client/graphql';
import { ExistingResultType } from '~/client/util';

import { getShippingCountries } from '../_actions/get-shipping-countries';
import { ShippingInfo } from '../shipping-info';
import { ShippingOptions } from '../shipping-options';

import { ShippingInfo } from './shipping-info';
import { ShippingOptions } from './shipping-options';
import { ShippingEstimatorFragment } from './fragment';
import { getShippingCountries } from './get-shipping-countries';

export const ShippingEstimator = ({
checkout,
shippingCountries,
}: {
checkout: ExistingResultType<typeof getCheckout>;
interface Props {
checkout: FragmentOf<typeof ShippingEstimatorFragment>;
shippingCountries: ExistingResultType<typeof getShippingCountries>;
}) => {
}

export const ShippingEstimator = ({ checkout, shippingCountries }: Props) => {
const t = useTranslations('Cart.CheckoutSummary');
const format = useFormatter();

Expand Down Expand Up @@ -97,13 +97,13 @@ export const ShippingEstimator = ({

{showShippingOptions && checkout.shippingConsignments && (
<div className="flex flex-col" id="shipping-options">
{checkout.shippingConsignments.map(({ entityId, availableShippingOptions }) => {
{checkout.shippingConsignments.map((consignment) => {
return (
<ShippingOptions
availableShippingOptions={availableShippingOptions}
checkout={checkout}
consignmentEntityId={entityId}
key={entityId}
checkoutEntityId={checkout.entityId}
currencyCode={checkout.cart?.currencyCode}
data={consignment}
key={consignment.entityId}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { graphql } from '~/client/graphql';

export const ShippingInfoFragment = graphql(`
fragment ShippingInfoFragment on Checkout {
entityId
shippingConsignments {
entityId
selectedShippingOption {
entityId
}
address {
city
countryCode
stateOrProvince
postalCode
}
}
cart {
lineItems {
physicalItems {
entityId
quantity
}
}
}
}
`);
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import { Input } from '@bigcommerce/components/input';
import { Select, SelectContent, SelectItem } from '@bigcommerce/components/select';
import { AlertCircle, Loader2 as Spinner } from 'lucide-react';
import { useTranslations } from 'next-intl';
import React, { useEffect, useReducer } from 'react';
import { useEffect, useReducer } from 'react';
import { useFormStatus } from 'react-dom';
import { toast } from 'react-hot-toast';

import { getShippingCountries } from '~/app/[locale]/(default)/cart/_actions/get-shipping-countries';
import { getCheckout } from '~/client/queries/get-checkout';
import { getShippingCountries } from '~/app/[locale]/(default)/cart/_components/shipping-estimator/get-shipping-countries';
import { FragmentOf } from '~/client/graphql';
import { ExistingResultType } from '~/client/util';
import { cn } from '~/lib/utils';

import { getShippingStates } from '../_actions/get-shipping-states';
import { submitShippingInfo } from '../_actions/submit-shipping-info';
import { ShippingInfoFragment } from './fragment';
import { getShippingStates } from './get-shipping-states';
import { submitShippingInfo } from './submit-shipping-info';

type StatesList = Array<{
id: number;
Expand Down Expand Up @@ -55,7 +56,7 @@ export const ShippingInfo = ({
isVisible,
hideShippingOptions,
}: {
checkout: ExistingResultType<typeof getCheckout>;
checkout: FragmentOf<typeof ShippingInfoFragment>;
shippingCountries: ExistingResultType<typeof getShippingCountries>;
isVisible: boolean;
hideShippingOptions: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { graphql } from '~/client/graphql';

export const ShippingOptionsFragment = graphql(`
fragment ShippingOptionsFragment on CheckoutShippingConsignment {
entityId
availableShippingOptions {
cost {
value
}
description
entityId
isRecommended
}
}
`);
Loading

0 comments on commit 53428a9

Please sign in to comment.