diff --git a/.changeset/plenty-memes-begin.md b/.changeset/plenty-memes-begin.md new file mode 100644 index 00000000..f3c6f5c5 --- /dev/null +++ b/.changeset/plenty-memes-begin.md @@ -0,0 +1,5 @@ +--- +"@godaddy/react": patch +--- + +Add support for locale prop on context provider diff --git a/packages/react/src/components/checkout/form/checkout-form.tsx b/packages/react/src/components/checkout/form/checkout-form.tsx index 80ef34e4..d9825c11 100644 --- a/packages/react/src/components/checkout/form/checkout-form.tsx +++ b/packages/react/src/components/checkout/form/checkout-form.tsx @@ -38,7 +38,7 @@ import { ShippingMethodForm } from '@/components/checkout/shipping/shipping-meth import { Target } from '@/components/checkout/target/target'; import { TipsForm } from '@/components/checkout/tips/tips-form'; import { DraftOrderTotals } from '@/components/checkout/totals/totals'; -import { formatCurrency } from '@/components/checkout/utils/format-currency'; +import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; import { Accordion, AccordionContent, @@ -71,6 +71,7 @@ export function CheckoutForm({ items, ...props }: CheckoutFormProps) { + const formatCurrency = useFormatCurrency(); const { t } = useGoDaddyContext(); const { session, isCheckoutDisabled } = useCheckoutContext(); @@ -439,7 +440,7 @@ export function CheckoutForm({ {formatCurrency({ amount: totals?.total?.value || 0, currencyCode, - isInCents: true, + inputInMinorUnits: true, })} @@ -449,11 +450,11 @@ export function CheckoutForm({ & { export interface DraftOrderLineItemsProps { items: Product[]; currencyCode?: string; - isInCents?: boolean; + inputInMinorUnits?: boolean; } export function DraftOrderLineItems({ items, currencyCode = 'USD', - isInCents = false, + inputInMinorUnits = false, }: DraftOrderLineItemsProps) { const { t } = useGoDaddyContext(); + const formatCurrency = useFormatCurrency(); return (
@@ -135,7 +136,7 @@ export function DraftOrderLineItems({ {formatCurrency({ amount: item.originalPrice * item.quantity, currencyCode, - isInCents, + inputInMinorUnits, })}
diff --git a/packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx b/packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx index a973aaec..ea5c8090 100644 --- a/packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx +++ b/packages/react/src/components/checkout/payment/checkout-buttons/express/godaddy.tsx @@ -34,7 +34,7 @@ import { filterAndSortShippingMethods } from '@/components/checkout/shipping/uti import { useGetShippingMethodByAddress } from '@/components/checkout/shipping/utils/use-get-shipping-methods'; import { useGetTaxes } from '@/components/checkout/taxes/utils/use-get-taxes'; import { mapOrderToFormValues } from '@/components/checkout/utils/checkout-transformers'; -import { formatCurrency } from '@/components/checkout/utils/format-currency'; +import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; import { Skeleton } from '@/components/ui/skeleton'; import { useGoDaddyContext } from '@/godaddy-provider'; import { GraphQLErrorWithCodes } from '@/lib/graphql-with-errors'; @@ -46,6 +46,7 @@ import { } from '@/tracking/track'; export function ExpressCheckoutButton() { + const formatCurrency = useFormatCurrency(); const { session, setCheckoutErrors } = useCheckoutContext(); const { isPoyntLoaded } = useLoadPoyntCollect(); const { godaddyPaymentsConfig } = useCheckoutContext(); @@ -139,7 +140,7 @@ export function ExpressCheckoutButton() { const shippingMethodPrice = formatCurrency({ amount: method.cost?.value || 0, currencyCode: method.cost?.currencyCode || currencyCode, - isInCents: true, + inputInMinorUnits: true, }); return { @@ -151,7 +152,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: method.cost?.value || 0, currencyCode: method.cost?.currencyCode || currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), amountInMinorUnits: method.cost?.value || 0, // Keep original minor unit value @@ -186,7 +187,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: -priceAdjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), isPending: false, @@ -199,7 +200,7 @@ export function ExpressCheckoutButton() { const totalAmount = formatCurrency({ amount: totalInMinorUnits, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }); @@ -217,7 +218,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: -priceAdjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }, @@ -356,7 +357,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: priceAdjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }; @@ -553,7 +554,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: godaddyTotals.shipping.value, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }); @@ -565,7 +566,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: godaddyTotals.taxes.value, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }); @@ -580,7 +581,7 @@ export function ExpressCheckoutButton() { const totalAmount = formatCurrency({ amount: totalInMinorUnits, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }); @@ -631,7 +632,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: godaddyTotals.shipping.value, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }); @@ -643,7 +644,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: godaddyTotals.taxes.value, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }); @@ -655,7 +656,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: -adjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }); @@ -670,7 +671,7 @@ export function ExpressCheckoutButton() { const totalAmount = formatCurrency({ amount: totalInMinorUnits, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }); @@ -686,7 +687,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: -adjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }, @@ -980,7 +981,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: taxesResult.value, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), isPending: false, @@ -1016,7 +1017,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: -priceAdjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), isPending: false, @@ -1045,7 +1046,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: -priceAdjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }; @@ -1161,7 +1162,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: taxesResult.value, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), isPending: false, @@ -1200,7 +1201,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: -priceAdjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), isPending: false, @@ -1230,7 +1231,7 @@ export function ExpressCheckoutButton() { amount: formatCurrency({ amount: -priceAdjustment, currencyCode, - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }), }; diff --git a/packages/react/src/components/checkout/payment/payment-form.tsx b/packages/react/src/components/checkout/payment/payment-form.tsx index 7a29e5a8..c1d7d0e8 100644 --- a/packages/react/src/components/checkout/payment/payment-form.tsx +++ b/packages/react/src/components/checkout/payment/payment-form.tsx @@ -34,7 +34,7 @@ import { DraftOrderTotals, type DraftOrderTotalsProps, } from '@/components/checkout/totals/totals'; -import { formatCurrency } from '@/components/checkout/utils/format-currency'; +import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; import { Accordion, AccordionContent, @@ -72,6 +72,7 @@ const PAYMENT_METHOD_ICONS: Record = { export function PaymentForm( props: DraftOrderTotalsProps & { items: Product[] } ) { + const formatCurrency = useFormatCurrency(); const { t } = useGoDaddyContext(); const { session, @@ -451,7 +452,7 @@ export function PaymentForm( {formatCurrency({ amount: props.total || 0, currencyCode: props.currencyCode || 'USD', - isInCents: true, + inputInMinorUnits: true, })} @@ -461,7 +462,7 @@ export function PaymentForm( @@ -469,7 +470,7 @@ export function PaymentForm(
)} @@ -307,7 +308,7 @@ export function ShippingMethodForm() { {formatCurrency({ amount: method?.cost?.value || 0, currencyCode: method?.cost?.currencyCode || 'USD', - isInCents: true, + inputInMinorUnits: true, })} )} diff --git a/packages/react/src/components/checkout/tips/tips-form.tsx b/packages/react/src/components/checkout/tips/tips-form.tsx index 7451436d..4d015870 100644 --- a/packages/react/src/components/checkout/tips/tips-form.tsx +++ b/packages/react/src/components/checkout/tips/tips-form.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { useCheckoutContext } from '@/components/checkout/checkout'; -import { formatCurrency } from '@/components/checkout/utils/format-currency'; +import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; import { Button } from '@/components/ui/button'; import { FormControl, @@ -25,6 +25,7 @@ export function TipsForm({ total, currencyCode }: TipsFormProps) { const { t } = useGoDaddyContext(); const { requiredFields } = useCheckoutContext(); const form = useFormContext(); + const formatCurrency = useFormatCurrency(); const [showCustomTip, setShowCustomTip] = useState(false); const calculateTipAmount = (percentage: number): number => { @@ -113,7 +114,7 @@ export function TipsForm({ total, currencyCode }: TipsFormProps) { {formatCurrency({ amount: calculateTipAmount(percentage), currencyCode: currencyCode || 'USD', - isInCents: true, + inputInMinorUnits: true, })} @@ -171,7 +172,7 @@ export function TipsForm({ total, currencyCode }: TipsFormProps) { ? formatCurrency({ amount: field.value, currencyCode: currencyCode || 'USD', - isInCents: true, + inputInMinorUnits: true, returnRaw: true, }) : '' diff --git a/packages/react/src/components/checkout/totals/totals.tsx b/packages/react/src/components/checkout/totals/totals.tsx index 3ae210b6..acb0ee32 100644 --- a/packages/react/src/components/checkout/totals/totals.tsx +++ b/packages/react/src/components/checkout/totals/totals.tsx @@ -1,6 +1,6 @@ import { DiscountStandalone } from '@/components/checkout/discount/discount-standalone'; import { TotalLineItemSkeleton } from '@/components/checkout/totals/totals-skeleton'; -import { formatCurrency } from '@/components/checkout/utils/format-currency'; +import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; import { useGoDaddyContext } from '@/godaddy-provider'; export interface DraftOrderTotalsProps { @@ -19,7 +19,7 @@ export interface DraftOrderTotalsProps { enableTaxes?: boolean | null; enableDiscounts?: boolean | null; enableShipping?: boolean | null; - isInCents?: boolean; + inputInMinorUnits?: boolean; } function TotalLineItem({ @@ -27,14 +27,16 @@ function TotalLineItem({ description, value, currencyCode = 'USD', - isInCents = false, + inputInMinorUnits = false, }: { title: string; description?: string; value: number; currencyCode?: string; - isInCents?: boolean; + inputInMinorUnits?: boolean; }) { + const formatCurrency = useFormatCurrency(); + return (
@@ -47,7 +49,7 @@ function TotalLineItem({ {formatCurrency({ amount: value, currencyCode, - isInCents, + inputInMinorUnits, })}
@@ -69,9 +71,10 @@ export function DraftOrderTotals({ isShippingLoading = false, isDiscountLoading = false, enableShipping = true, - isInCents = false, + inputInMinorUnits = false, }: DraftOrderTotalsProps) { const { t } = useGoDaddyContext(); + const formatCurrency = useFormatCurrency(); const handleDiscountsChange = (discounts: string[]) => { // Discount changes are handled by the DiscountStandalone component }; @@ -88,7 +91,7 @@ export function DraftOrderTotals({ : t.totals.noItems } value={subtotal} - isInCents={isInCents} + inputInMinorUnits={inputInMinorUnits} /> {discount > 0 ? ( isDiscountLoading ? ( @@ -98,7 +101,7 @@ export function DraftOrderTotals({ currencyCode={currencyCode} title={t.totals.discount} value={-discount || 0} - isInCents={isInCents} + inputInMinorUnits={inputInMinorUnits} /> ) ) : null} @@ -110,7 +113,7 @@ export function DraftOrderTotals({ currencyCode={currencyCode} title={t.totals.shipping} value={shipping || 0} - isInCents={isInCents} + inputInMinorUnits={inputInMinorUnits} /> ))} {tip ? ( @@ -118,7 +121,7 @@ export function DraftOrderTotals({ currencyCode={currencyCode} title={t.totals.tip} value={tip || 0} - isInCents={isInCents} + inputInMinorUnits={inputInMinorUnits} /> ) : null} {enableTaxes && @@ -129,7 +132,7 @@ export function DraftOrderTotals({ currencyCode={currencyCode} title={t.totals.estimatedTaxes} value={taxes || 0} - isInCents={isInCents} + inputInMinorUnits={inputInMinorUnits} /> ))}
@@ -157,7 +160,7 @@ export function DraftOrderTotals({ {formatCurrency({ amount: total, currencyCode, - isInCents, + inputInMinorUnits, })}
diff --git a/packages/react/src/components/checkout/utils/format-currency.ts b/packages/react/src/components/checkout/utils/format-currency.ts index 36fbf69d..e974ad69 100644 --- a/packages/react/src/components/checkout/utils/format-currency.ts +++ b/packages/react/src/components/checkout/utils/format-currency.ts @@ -1,3 +1,5 @@ +import { useGoDaddyContext } from '@/godaddy-provider'; + /** * Currency configuration map with symbols and decimal precision. */ @@ -49,7 +51,7 @@ export interface FormatCurrencyOptions { * - true → format to currency string (default) * - false → convert to minor units and return as string */ - isInCents?: boolean; + inputInMinorUnits?: boolean; /** * Return raw numeric value without currency symbol. * - true → returns "10.00" instead of "$10.00" @@ -61,22 +63,22 @@ export interface FormatCurrencyOptions { /** * Formats or converts a currency amount. * - * - When `isInCents = true` (default): returns formatted string like "$123.45" - * - When `isInCents = false`: returns string representing minor units like "12345" + * - When `inputInMinorUnits = true` (default): returns formatted string like "$123.45" + * - When `inputInMinorUnits = false`: returns string representing minor units like "12345" * - When `returnRaw = true`: returns numeric value without currency symbol like "123.45" */ export function formatCurrency({ amount, currencyCode, locale = 'en-US', - isInCents = true, + inputInMinorUnits = true, returnRaw = false, }: FormatCurrencyOptions): string { const config = currencyConfigs[currencyCode] || {}; const { precision = 2 } = config; - if (!isInCents) { + if (!inputInMinorUnits) { // Convert major units to minor units and return as string return Math.round(amount * Math.pow(10, precision)).toString(); } @@ -86,11 +88,37 @@ export function formatCurrency({ const nfLocale = returnRaw ? 'en-US' : locale; - return new Intl.NumberFormat(nfLocale, { - style: returnRaw ? 'decimal' : 'currency', - currency: returnRaw ? undefined : currencyCode, - minimumFractionDigits: precision, - maximumFractionDigits: precision, - useGrouping: returnRaw ? false : undefined, - }).format(value); + try { + return new Intl.NumberFormat(nfLocale, { + style: returnRaw ? 'decimal' : 'currency', + currency: returnRaw ? undefined : currencyCode, + minimumFractionDigits: precision, + maximumFractionDigits: precision, + useGrouping: returnRaw ? false : undefined, + }).format(value); + } catch (_error) { + // Fallback to 'en-US' for invalid locales + return new Intl.NumberFormat('en-US', { + style: returnRaw ? 'decimal' : 'currency', + currency: returnRaw ? undefined : currencyCode, + minimumFractionDigits: precision, + maximumFractionDigits: precision, + useGrouping: returnRaw ? false : undefined, + }).format(value); + } +} + +/** + * Hook that returns a formatCurrency function using the locale from GoDaddyProvider context. + * The returned function has the same signature as formatCurrency, but uses the context locale as default. + */ +export function useFormatCurrency() { + const { locale: contextLocale } = useGoDaddyContext(); + + return (options: FormatCurrencyOptions) => { + return formatCurrency({ + ...options, + locale: options.locale ?? contextLocale ?? 'en-US', + }); + }; } diff --git a/packages/react/src/components/storefront/product-card.tsx b/packages/react/src/components/storefront/product-card.tsx index 676a5406..0b642ec1 100644 --- a/packages/react/src/components/storefront/product-card.tsx +++ b/packages/react/src/components/storefront/product-card.tsx @@ -1,11 +1,11 @@ 'use client'; import { ChevronRight, ShoppingBag } from 'lucide-react'; +import { useFormatCurrency } from '@/components/checkout/utils/format-currency'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { RouterLink } from '@/components/ui/link'; -import { formatCurrency } from '@/lib/utils'; import { SKUGroup } from '@/types.ts'; interface ProductCardProps { @@ -14,6 +14,7 @@ interface ProductCardProps { } export function ProductCard({ product, href }: ProductCardProps) { + const formatCurrency = useFormatCurrency(); const title = product?.label || product?.name || 'Product'; const description = product?.description || ''; const priceMin = product?.priceRange?.min || 0; @@ -61,8 +62,12 @@ export function ProductCard({ product, href }: ProductCardProps) {
{isPriceRange - ? `${formatCurrency(priceMin)} - ${formatCurrency(priceMax)}` - : formatCurrency(priceMin)} + ? `${formatCurrency({ amount: priceMin, currencyCode: 'USD', inputInMinorUnits: true })} - ${formatCurrency({ amount: priceMax, currencyCode: 'USD', inputInMinorUnits: true })}` + : formatCurrency({ + amount: priceMin, + currencyCode: 'USD', + inputInMinorUnits: true, + })} {hasOptions ? (