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.
This commit is contained in:
parent
0233ff2dce
commit
0a2cafed76
@ -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 (
|
||||
<div className="bg-gradient-to-br from-slate-50 to-slate-100 rounded-2xl border border-slate-200 shadow-sm overflow-hidden">
|
||||
@ -22,13 +20,13 @@ export function InvoiceTotals({ subtotal, tax, total }: InvoiceTotalsProps) {
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center text-slate-600">
|
||||
<span className="font-medium">Subtotal</span>
|
||||
<span className="font-semibold text-slate-900">{fmt(subtotal)}</span>
|
||||
<span className="font-semibold text-slate-900">{formatCurrency(subtotal)}</span>
|
||||
</div>
|
||||
|
||||
{tax > 0 && (
|
||||
<div className="flex justify-between items-center text-slate-600">
|
||||
<span className="font-medium">Tax</span>
|
||||
<span className="font-semibold text-slate-900">{fmt(tax)}</span>
|
||||
<span className="font-semibold text-slate-900">{formatCurrency(tax)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -36,7 +34,7 @@ export function InvoiceTotals({ subtotal, tax, total }: InvoiceTotalsProps) {
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-xl font-bold text-slate-900">Total Amount</span>
|
||||
<div className="flex items-baseline gap-2">
|
||||
<div className="text-3xl font-bold text-slate-900">{fmt(total)}</div>
|
||||
<div className="text-3xl font-bold text-slate-900">{formatCurrency(total)}</div>
|
||||
<div className="text-lg font-medium text-slate-500">JPY</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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 (
|
||||
<div id="attention" className="bg-white rounded-xl border border-orange-200 shadow-sm p-4">
|
||||
<div className="flex items-center gap-4">
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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<HTMLDivElement, SubscriptionCardProps>(
|
||||
({ subscription, variant = "list", showActions = true, onViewClick, className }, ref) => {
|
||||
const { formatCurrency } = useFormatCurrency();
|
||||
|
||||
const handleViewClick = () => {
|
||||
if (onViewClick) {
|
||||
onViewClick(subscription);
|
||||
|
||||
@ -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<HTMLDivElement, SubscriptionDetailsProps>(
|
||||
({ subscription, showServiceSpecificSections = true, className }, ref) => {
|
||||
const { formatCurrency } = useFormatCurrency();
|
||||
|
||||
return (
|
||||
<div ref={ref} className={cn("space-y-6", className)}>
|
||||
{/* Main Details Card */}
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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<CurrencyInfo[]> {
|
||||
@ -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[];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<SupportedCurrency, string> = {
|
||||
JPY: "ja-JP",
|
||||
USD: "en-US",
|
||||
EUR: "de-DE",
|
||||
};
|
||||
return localeMap[currency] || "en-US";
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user