From 0a2cafed76bd2030c709938c7b860baa21562c52 Mon Sep 17 00:00:00 2001 From: barsa Date: Mon, 20 Oct 2025 14:01:29 +0900 Subject: [PATCH] Refactor currency formatting across multiple components to utilize the new useFormatCurrency hook. This change enhances consistency in currency display and improves maintainability by centralizing currency formatting logic. Updated relevant components to ensure they correctly format amounts with the appropriate currency symbol. --- .../InvoiceDetail/InvoiceTotals.tsx | 12 +++--- .../components/UpcomingPaymentBanner.tsx | 5 ++- .../dashboard/views/DashboardView.tsx | 3 +- .../components/SubscriptionCard.tsx | 5 ++- .../components/SubscriptionDetails.tsx | 5 ++- .../subscriptions/views/SubscriptionsList.tsx | 3 +- .../portal/src/lib/hooks/useFormatCurrency.ts | 6 +-- .../src/lib/services/currency.service.ts | 4 +- .../domain/toolkit/formatting/currency.ts | 41 ++++++------------- packages/domain/toolkit/index.ts | 2 +- 10 files changed, 37 insertions(+), 49 deletions(-) diff --git a/apps/portal/src/features/billing/components/InvoiceDetail/InvoiceTotals.tsx b/apps/portal/src/features/billing/components/InvoiceDetail/InvoiceTotals.tsx index 0619c28e..17188e9d 100644 --- a/apps/portal/src/features/billing/components/InvoiceDetail/InvoiceTotals.tsx +++ b/apps/portal/src/features/billing/components/InvoiceDetail/InvoiceTotals.tsx @@ -1,9 +1,7 @@ "use client"; import React from "react"; -import { Formatting } from "@customer-portal/domain/toolkit"; - -const { formatCurrency } = Formatting; +import { useFormatCurrency } from "@/lib/hooks/useFormatCurrency"; interface InvoiceTotalsProps { subtotal: number; @@ -12,7 +10,7 @@ interface InvoiceTotalsProps { } export function InvoiceTotals({ subtotal, tax, total }: InvoiceTotalsProps) { - const fmt = (amount: number) => formatCurrency(amount); + const { formatCurrency } = useFormatCurrency(); return (
@@ -22,13 +20,13 @@ export function InvoiceTotals({ subtotal, tax, total }: InvoiceTotalsProps) {
Subtotal - {fmt(subtotal)} + {formatCurrency(subtotal)}
{tax > 0 && (
Tax - {fmt(tax)} + {formatCurrency(tax)}
)} @@ -36,7 +34,7 @@ export function InvoiceTotals({ subtotal, tax, total }: InvoiceTotalsProps) {
Total Amount
-
{fmt(total)}
+
{formatCurrency(total)}
JPY
diff --git a/apps/portal/src/features/dashboard/components/UpcomingPaymentBanner.tsx b/apps/portal/src/features/dashboard/components/UpcomingPaymentBanner.tsx index abeb647b..2a1fbef3 100644 --- a/apps/portal/src/features/dashboard/components/UpcomingPaymentBanner.tsx +++ b/apps/portal/src/features/dashboard/components/UpcomingPaymentBanner.tsx @@ -3,9 +3,8 @@ import Link from "next/link"; import { CalendarDaysIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; import { format, formatDistanceToNow } from "date-fns"; -import { Formatting } from "@customer-portal/domain/toolkit"; -const { formatCurrency, getCurrencyLocale } = Formatting; +import { useFormatCurrency } from "@/lib/hooks/useFormatCurrency"; interface UpcomingPaymentBannerProps { invoice: { id: number; amount: number; currency?: string; dueDate: string }; @@ -14,6 +13,8 @@ interface UpcomingPaymentBannerProps { } export function UpcomingPaymentBanner({ invoice, onPay, loading }: UpcomingPaymentBannerProps) { + const { formatCurrency } = useFormatCurrency(); + return (
diff --git a/apps/portal/src/features/dashboard/views/DashboardView.tsx b/apps/portal/src/features/dashboard/views/DashboardView.tsx index a73cdcd4..b0521ff9 100644 --- a/apps/portal/src/features/dashboard/views/DashboardView.tsx +++ b/apps/portal/src/features/dashboard/views/DashboardView.tsx @@ -27,11 +27,12 @@ import { LoadingStats, LoadingTable } from "@/components/atoms"; import { ErrorState } from "@/components/atoms/error-state"; import { Formatting } from "@customer-portal/domain/toolkit"; -const { formatCurrency, getCurrencyLocale } = Formatting; +import { useFormatCurrency } from "@/lib/hooks/useFormatCurrency"; import { log } from "@customer-portal/logging"; import { useCreateInvoiceSsoLink } from "@/features/billing/hooks/useBilling"; export function DashboardView() { + const { formatCurrency } = useFormatCurrency(); const router = useRouter(); const { user, isAuthenticated, loading: authLoading, clearLoading } = useAuthStore(); diff --git a/apps/portal/src/features/subscriptions/components/SubscriptionCard.tsx b/apps/portal/src/features/subscriptions/components/SubscriptionCard.tsx index f5b22bed..06961dea 100644 --- a/apps/portal/src/features/subscriptions/components/SubscriptionCard.tsx +++ b/apps/portal/src/features/subscriptions/components/SubscriptionCard.tsx @@ -15,10 +15,9 @@ import { import { StatusPill } from "@/components/atoms/status-pill"; import { Button } from "@/components/atoms/button"; import { SubCard } from "@/components/molecules/SubCard/SubCard"; -import { Formatting } from "@customer-portal/domain/toolkit"; import type { Subscription } from "@customer-portal/domain/subscriptions"; -const { formatCurrency, getCurrencyLocale } = Formatting; +import { useFormatCurrency } from "@/lib/hooks/useFormatCurrency"; import { cn } from "@/lib/utils"; interface SubscriptionCardProps { @@ -78,6 +77,8 @@ const getBillingCycleLabel = (cycle: string) => { export const SubscriptionCard = forwardRef( ({ subscription, variant = "list", showActions = true, onViewClick, className }, ref) => { + const { formatCurrency } = useFormatCurrency(); + const handleViewClick = () => { if (onViewClick) { onViewClick(subscription); diff --git a/apps/portal/src/features/subscriptions/components/SubscriptionDetails.tsx b/apps/portal/src/features/subscriptions/components/SubscriptionDetails.tsx index 54ea5c85..478ad2e9 100644 --- a/apps/portal/src/features/subscriptions/components/SubscriptionDetails.tsx +++ b/apps/portal/src/features/subscriptions/components/SubscriptionDetails.tsx @@ -15,10 +15,9 @@ import { } from "@heroicons/react/24/outline"; import { StatusPill } from "@/components/atoms/status-pill"; import { SubCard } from "@/components/molecules/SubCard/SubCard"; -import { Formatting } from "@customer-portal/domain/toolkit"; import type { Subscription } from "@customer-portal/domain/subscriptions"; -const { formatCurrency, getCurrencyLocale } = Formatting; +import { useFormatCurrency } from "@/lib/hooks/useFormatCurrency"; import { cn } from "@/lib/utils"; interface SubscriptionDetailsProps { @@ -104,6 +103,8 @@ const isVpnService = (productName: string) => { export const SubscriptionDetails = forwardRef( ({ subscription, showServiceSpecificSections = true, className }, ref) => { + const { formatCurrency } = useFormatCurrency(); + return (
{/* Main Details Card */} diff --git a/apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx b/apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx index e54b8bb3..ff3176df 100644 --- a/apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx +++ b/apps/portal/src/features/subscriptions/views/SubscriptionsList.tsx @@ -26,10 +26,11 @@ import { useSubscriptions, useSubscriptionStats } from "@/features/subscriptions import { Formatting } from "@customer-portal/domain/toolkit"; import type { Subscription } from "@customer-portal/domain/subscriptions"; -const { formatCurrency, getCurrencyLocale } = Formatting; +import { useFormatCurrency } from "@/lib/hooks/useFormatCurrency"; export function SubscriptionsListContainer() { const router = useRouter(); + const { formatCurrency } = useFormatCurrency(); const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); diff --git a/apps/portal/src/lib/hooks/useFormatCurrency.ts b/apps/portal/src/lib/hooks/useFormatCurrency.ts index 95df1e70..183906a0 100644 --- a/apps/portal/src/lib/hooks/useFormatCurrency.ts +++ b/apps/portal/src/lib/hooks/useFormatCurrency.ts @@ -4,7 +4,7 @@ import { useCurrency } from "@/lib/hooks/useCurrency"; import { formatCurrency as baseFormatCurrency } from "@customer-portal/domain/toolkit"; export function useFormatCurrency() { - const { currencyCode, loading, error } = useCurrency(); + const { currencyCode, currencySymbol, loading, error } = useCurrency(); const formatCurrency = (amount: number) => { if (loading) { @@ -14,11 +14,11 @@ export function useFormatCurrency() { if (error) { // Fallback to JPY if there's an error - return baseFormatCurrency(amount, "JPY"); + return baseFormatCurrency(amount, "JPY", "¥"); } // Use the currency from WHMCS API - return baseFormatCurrency(amount, currencyCode); + return baseFormatCurrency(amount, currencyCode, currencySymbol); }; return { diff --git a/apps/portal/src/lib/services/currency.service.ts b/apps/portal/src/lib/services/currency.service.ts index 90730b12..58141cd0 100644 --- a/apps/portal/src/lib/services/currency.service.ts +++ b/apps/portal/src/lib/services/currency.service.ts @@ -19,7 +19,7 @@ class CurrencyServiceImpl implements CurrencyService { if (!response.data) { throw new Error("Failed to get default currency"); } - return response.data; + return response.data as CurrencyInfo; } async getAllCurrencies(): Promise { @@ -27,7 +27,7 @@ class CurrencyServiceImpl implements CurrencyService { if (!response.data) { throw new Error("Failed to get currencies"); } - return response.data; + return response.data as CurrencyInfo[]; } } diff --git a/packages/domain/toolkit/formatting/currency.ts b/packages/domain/toolkit/formatting/currency.ts index 8d5b7fb1..aec4d22a 100644 --- a/packages/domain/toolkit/formatting/currency.ts +++ b/packages/domain/toolkit/formatting/currency.ts @@ -8,36 +8,33 @@ export type SupportedCurrency = "JPY" | "USD" | "EUR"; /** - * Format a number as currency using WHMCS default currency + * Format a number as currency using WHMCS currency data * * @param amount - The numeric amount to format - * @param currencyCode - Optional currency code (defaults to WHMCS default) - * @param locale - Optional locale (defaults to currency-specific locale) + * @param currencyCode - Currency code from WHMCS API (e.g., "JPY", "USD", "EUR") + * @param currencyPrefix - Currency symbol from WHMCS API (e.g., "¥", "$", "€") * * @example - * formatCurrency(1000) // Uses WHMCS default currency - * formatCurrency(1000, "USD") // Uses specific currency - * formatCurrency(1000, "JPY", "ja-JP") // Uses specific currency and locale + * formatCurrency(1000, "JPY", "¥") // ¥1,000 + * formatCurrency(1000, "USD", "$") // $1,000.00 + * formatCurrency(1000, "EUR", "€") // €1,000.00 */ export function formatCurrency( amount: number, - currencyCode: string = "JPY", - locale?: string + currencyCode: string, + currencyPrefix: string ): string { - // Use provided locale or get from currency - const currencyLocale = locale || getCurrencyLocale(currencyCode as SupportedCurrency); - // Determine fraction digits based on currency const fractionDigits = currencyCode === "JPY" ? 0 : 2; - - const formatter = new Intl.NumberFormat(currencyLocale, { - style: "currency", - currency: currencyCode, + + // Format the number with appropriate decimal places + const formattedAmount = amount.toLocaleString("en-US", { minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits, }); - return formatter.format(amount); + // Add currency prefix + return `${currencyPrefix}${formattedAmount}`; } /** @@ -50,15 +47,3 @@ export function parseCurrency(value: string): number | null { return Number.isFinite(parsed) ? parsed : null; } -/** - * Get the locale string for a given currency - */ -export function getCurrencyLocale(currency: SupportedCurrency = "JPY"): string { - const localeMap: Record = { - JPY: "ja-JP", - USD: "en-US", - EUR: "de-DE", - }; - return localeMap[currency] || "en-US"; -} - diff --git a/packages/domain/toolkit/index.ts b/packages/domain/toolkit/index.ts index 365d983c..bf171701 100644 --- a/packages/domain/toolkit/index.ts +++ b/packages/domain/toolkit/index.ts @@ -9,7 +9,7 @@ export * as Validation from "./validation/index"; export * as Typing from "./typing/index"; // Re-export commonly used utilities for convenience -export { formatCurrency, getCurrencyLocale } from "./formatting/currency"; +export { formatCurrency } from "./formatting/currency"; export type { SupportedCurrency } from "./formatting/currency"; // Re-export AsyncState types and helpers