Skip to content

Commit

Permalink
feat(core): use next-intl to format dates and currencies
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgemoya committed Apr 23, 2024
1 parent 5655f81 commit 9020c09
Show file tree
Hide file tree
Showing 14 changed files with 226 additions and 123 deletions.
5 changes: 5 additions & 0 deletions .changeset/curly-trees-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": patch
---

use next-intl formatter to properly localize dates & prices
8 changes: 5 additions & 3 deletions apps/core/app/[locale]/(default)/blog/[blogId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { Tag, TagContent } from '@bigcommerce/components/tag';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { getFormatter } from 'next-intl/server';

import { getBlogPost } from '~/client/queries/get-blog-post';
import { BcImage } from '~/components/bc-image';
Expand All @@ -32,7 +33,8 @@ export async function generateMetadata({ params: { blogId } }: Props): Promise<M
};
}

export default async function BlogPostPage({ params: { blogId } }: Props) {
export default async function BlogPostPage({ params: { blogId, locale } }: Props) {
const format = await getFormatter({ locale });
const blogPost = await getBlogPost(+blogId);

if (!blogPost || !blogPost.isVisibleInNavigation) {
Expand All @@ -45,7 +47,7 @@ export default async function BlogPostPage({ params: { blogId } }: Props) {

<div className="mb-8 flex">
<BlogPostDate className="mb-0">
{new Intl.DateTimeFormat('en-US').format(new Date(blogPost.publishedDate.utc))}
{format.dateTime(new Date(blogPost.publishedDate.utc))}
</BlogPostDate>
{blogPost.author ? <BlogPostAuthor>, by {blogPost.author}</BlogPostAuthor> : null}
</div>
Expand All @@ -67,7 +69,7 @@ export default async function BlogPostPage({ params: { blogId } }: Props) {
</BlogPostTitle>
<BlogPostDate variant="inBanner">
<span className="text-primary">
{new Intl.DateTimeFormat('en-US').format(new Date(blogPost.publishedDate.utc))}
{format.dateTime(new Date(blogPost.publishedDate.utc))}
</span>
</BlogPostDate>
</BlogPostBanner>
Expand Down
15 changes: 7 additions & 8 deletions apps/core/app/[locale]/(default)/cart/_components/cart-item.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { getFormatter, getMessages } from 'next-intl/server';

import { getCart } from '~/client/queries/get-cart';
import { ExistingResultType } from '~/client/util';
Expand All @@ -22,11 +22,7 @@ export const CartItem = async ({
locale: string;
}) => {
const messages = await getMessages({ locale });

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

return (
<li>
Expand Down Expand Up @@ -88,7 +84,7 @@ export const CartItem = async ({
<div key={selectedOption.entityId}>
<span>{selectedOption.name}:</span>{' '}
<span className="font-semibold">
{Intl.DateTimeFormat().format(new Date(selectedOption.date.utc))}
{format.dateTime(new Date(selectedOption.date.utc))}
</span>
</div>
);
Expand All @@ -106,7 +102,10 @@ export const CartItem = async ({

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AlertCircle } from 'lucide-react';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages, getTranslations } from 'next-intl/server';
import { getFormatter, getMessages, getTranslations } from 'next-intl/server';
import { toast } from 'react-hot-toast';

import { getCheckout } from '~/client/queries/get-checkout';
Expand All @@ -12,6 +12,7 @@ import { ShippingEstimator } from './shipping-estimator';

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

const [checkout, shippingCountries] = await Promise.all([
Expand All @@ -27,16 +28,16 @@ export const CheckoutSummary = async ({ cartId, locale }: { cartId: string; loca
return null;
}

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

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

<NextIntlClientProvider locale={locale} messages={{ Cart: messages.Cart ?? {} }}>
Expand All @@ -46,7 +47,13 @@ export const CheckoutSummary = async ({ cartId, locale }: { cartId: string; loca
{checkout.cart?.discountedAmount && (
<div className="flex justify-between border-t border-t-gray-200 py-4">
<span className="font-semibold">{t('discounts')}</span>
<span>-{currencyFormatter.format(checkout.cart.discountedAmount.value)}</span>
<span>
-
{format.number(checkout.cart.discountedAmount.value, {
style: 'currency',
currency: checkout.cart.currencyCode,
})}
</span>
</div>
)}

Expand All @@ -57,13 +64,23 @@ export const CheckoutSummary = async ({ cartId, locale }: { cartId: string; loca
{checkout.taxTotal && (
<div className="flex justify-between border-t border-t-gray-200 py-4">
<span className="font-semibold">{t('tax')}</span>
<span>{currencyFormatter.format(checkout.taxTotal.value)}</span>
<span>
{format.number(checkout.taxTotal.value, {
style: 'currency',
currency: checkout.cart?.currencyCode,
})}
</span>
</div>
)}

<div className="flex justify-between border-t border-t-gray-200 py-4 text-xl font-bold lg:text-2xl">
{t('grandTotal')}
<span>{currencyFormatter.format(checkout.grandTotal?.value || 0)}</span>
<span>
{format.number(checkout.grandTotal?.value || 0, {
style: 'currency',
currency: checkout.cart?.currencyCode,
})}
</span>
</div>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button } from '@bigcommerce/components/button';
import { Field, FieldControl, FieldMessage, Form, FormSubmit } from '@bigcommerce/components/form';
import { Input } from '@bigcommerce/components/input';
import { AlertCircle, Loader2 as Spinner } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useFormatter, useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import { useFormStatus } from 'react-dom';
import { toast } from 'react-hot-toast';
Expand Down Expand Up @@ -37,16 +37,13 @@ const SubmitButton = () => {

export const CouponCode = ({ checkout }: { checkout: ExistingResultType<typeof getCheckout> }) => {
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 currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: checkout.cart?.currencyCode,
});

useEffect(() => {
if (checkout.coupons[0]) {
setSelectedCoupon(checkout.coupons[0]);
Expand Down Expand Up @@ -84,7 +81,12 @@ export const CouponCode = ({ checkout }: { checkout: ExistingResultType<typeof g
<span className="font-semibold">
{t('coupon')} ({selectedCoupon.code})
</span>
<span>{currencyFormatter.format(selectedCoupon.discountedAmount.value * -1)}</span>
<span>
{format.number(selectedCoupon.discountedAmount.value * -1, {
style: 'currency',
currency: checkout.cart?.currencyCode,
})}
</span>
</div>
<form action={onSubmitRemoveCouponCode}>
<input name="checkoutEntityId" type="hidden" value={checkout.entityId} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client';

import { Button } from '@bigcommerce/components/button';
import { useTranslations } from 'next-intl';
import { useFormatter, useTranslations } from 'next-intl';
import { useEffect, useRef, useState } from 'react';

import { getCheckout } from '~/client/queries/get-checkout';
Expand All @@ -20,15 +20,11 @@ export const ShippingEstimator = ({
shippingCountries: ExistingResultType<typeof getShippingCountries>;
}) => {
const t = useTranslations('Cart.CheckoutSummary');
const format = useFormatter();

const [showShippingInfo, setShowShippingInfo] = useState(false);
const [showShippingOptions, setShowShippingOptions] = useState(false);

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

const selectedShippingConsignment = checkout.shippingConsignments?.find(
(shippingConsignment) => shippingConsignment.selectedShippingOption,
);
Expand Down Expand Up @@ -60,7 +56,12 @@ export const ShippingEstimator = ({
<div className="flex justify-between">
<span className="font-semibold">{t('shippingCost')}</span>
{selectedShippingConsignment ? (
<span>{currencyFormatter.format(checkout.shippingCostTotal?.value || 0)}</span>
<span>
{format.number(checkout.shippingCostTotal?.value || 0, {
style: 'currency',
currency: checkout.cart?.currencyCode,
})}
</span>
) : (
<Button
aria-controls="shipping-options"
Expand Down Expand Up @@ -113,7 +114,12 @@ export const ShippingEstimator = ({
{Boolean(checkout.handlingCostTotal?.value) && (
<div className="flex justify-between border-t border-t-gray-200 py-4">
<span className="font-semibold">{t('handlingCost')}</span>
<span>{currencyFormatter.format(checkout.handlingCostTotal?.value || 0)}</span>
<span>
{format.number(checkout.handlingCostTotal?.value || 0, {
style: 'currency',
currency: checkout.cart?.currencyCode,
})}
</span>
</div>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Label } from '@bigcommerce/components/label';
import { Message } from '@bigcommerce/components/message';
import { RadioGroup, RadioItem } from '@bigcommerce/components/radio-group';
import { AlertCircle, Loader2 as Spinner } from 'lucide-react';
import { useTranslations } from 'next-intl';
import { useFormatter, useTranslations } from 'next-intl';
import { useFormStatus } from 'react-dom';
import { toast } from 'react-hot-toast';

Expand Down Expand Up @@ -50,6 +50,7 @@ export const ShippingOptions = ({
availableShippingOptions: AvailableShippingOptions[] | null;
}) => {
const t = useTranslations('Cart.ShippingCost');
const format = useFormatter();

const shippingOptions = availableShippingOptions?.map(
({ cost, description, entityId: shippingOptionEntityId, isRecommended }) => ({
Expand All @@ -60,11 +61,6 @@ export const ShippingOptions = ({
}),
);

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

const onSubmit = async (formData: FormData) => {
const { status } = await submitShippingCosts(formData, checkout.entityId, consignmentEntityId);

Expand Down Expand Up @@ -98,7 +94,12 @@ export const ShippingOptions = ({
>
<p className="inline-flex w-full justify-between">
<span>{option.description}</span>
<span>{currencyFormatter.format(option.cost)}</span>
<span>
{format.number(option.cost, {
style: 'currency',
currency: checkout.cart?.currencyCode,
})}
</span>
</p>
</Label>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useTranslations } from 'next-intl';
import { useFormatter, useTranslations } from 'next-intl';
import { Suspense } from 'react';

import { getProduct } from '~/client/queries/get-product';
Expand All @@ -11,15 +11,11 @@ type Product = Awaited<ReturnType<typeof getProduct>>;

export const Details = ({ product }: { product: NonNullable<Product> }) => {
const t = useTranslations('Product.Details');
const format = useFormatter();

const showPriceRange =
product.prices?.priceRange.min.value !== product.prices?.priceRange.max.value;

const currencyFormatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: product.prices?.price.currencyCode || 'USD',
});

return (
<div>
{product.brand && (
Expand All @@ -36,16 +32,26 @@ export const Details = ({ product }: { product: NonNullable<Product> }) => {
<div className="my-6 text-2xl font-bold lg:text-3xl">
{showPriceRange ? (
<span>
{currencyFormatter.format(product.prices.priceRange.min.value)} -{' '}
{currencyFormatter.format(product.prices.priceRange.max.value)}
{format.number(product.prices.priceRange.min.value, {
style: 'currency',
currency: product.prices.price.currencyCode,
})}{' '}
-{' '}
{format.number(product.prices.priceRange.max.value, {
style: 'currency',
currency: product.prices.price.currencyCode,
})}
</span>
) : (
<>
{product.prices.retailPrice?.value !== undefined && (
<span>
{t('Prices.msrp')}:{' '}
<span className="line-through">
{currencyFormatter.format(product.prices.retailPrice.value)}
{format.number(product.prices.retailPrice.value, {
style: 'currency',
currency: product.prices.price.currencyCode,
})}
</span>
<br />
</span>
Expand All @@ -56,17 +62,29 @@ export const Details = ({ product }: { product: NonNullable<Product> }) => {
<span>
{t('Prices.was')}:{' '}
<span className="line-through">
{currencyFormatter.format(product.prices.basePrice.value)}
{format.number(product.prices.basePrice.value, {
style: 'currency',
currency: product.prices.price.currencyCode,
})}
</span>
</span>
<br />
<span>
{t('Prices.now')} {currencyFormatter.format(product.prices.salePrice.value)}
{t('Prices.now')}{' '}
{format.number(product.prices.salePrice.value, {
style: 'currency',
currency: product.prices.price.currencyCode,
})}
</span>
</>
) : (
product.prices.price.value && (
<span>{currencyFormatter.format(product.prices.price.value)}</span>
<span>
{format.number(product.prices.price.value, {
style: 'currency',
currency: product.prices.price.currencyCode,
})}
</span>
)
)}
</>
Expand Down
Loading

0 comments on commit 9020c09

Please sign in to comment.