Skip to content

Commit 8512ab2

Browse files
committed
[TOOL-3485] Dashboard: Improved Currency formatting
1 parent 846a5b5 commit 8512ab2

File tree

8 files changed

+43
-26
lines changed

8 files changed

+43
-26
lines changed

apps/dashboard/src/components/pay/PayAnalytics/components/PayCustomersTable.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "@/components/ui/select";
1212
import { SkeletonContainer } from "@/components/ui/skeleton";
1313
import { useState } from "react";
14+
import { toUSD } from "../../../../utils/number";
1415
import {
1516
type PayTopCustomersData,
1617
usePayCustomers,
@@ -249,7 +250,7 @@ function getCSVData(data: PayTopCustomersData["customers"]) {
249250
const header = ["Wallet Address", "Total spend"];
250251
const rows = data.map((customer) => [
251252
customer.walletAddress,
252-
`$${(customer.totalSpendUSDCents / 100).toLocaleString()}`,
253+
toUSD(customer.totalSpendUSDCents / 100),
253254
]);
254255

255256
return { header, rows };

apps/dashboard/src/components/pay/PayAnalytics/components/PaymentsSuccessRate.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SkeletonContainer } from "@/components/ui/skeleton";
99
import { ToolTipLabel } from "@/components/ui/tooltip";
1010
import { cn } from "@/lib/utils";
1111
import { useState } from "react";
12+
import { toUSD } from "../../../../utils/number";
1213
import { usePayVolume } from "../hooks/usePayVolume";
1314
import { CardHeading, FailedToLoad } from "./common";
1415

@@ -229,7 +230,7 @@ function InfoRow(props: {
229230
props.isEmpty
230231
? "$-"
231232
: props.amount !== undefined
232-
? `$${props.amount.toLocaleString()}`
233+
? toUSD(props.amount)
233234
: undefined
234235
}
235236
skeletonData="$50"

apps/dashboard/src/components/pay/PayAnalytics/components/Payouts.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { SkeletonContainer } from "@/components/ui/skeleton";
22
import { format } from "date-fns";
33
import { useEffect, useState } from "react";
44
import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis } from "recharts";
5+
import { toUSD } from "../../../../utils/number";
56
import { AreaChartLoadingState } from "../../../analytics/area-chart";
67
import { usePayVolume } from "../hooks/usePayVolume";
78
import {
@@ -144,7 +145,7 @@ function RenderData(props: {
144145
props.query.isEmpty
145146
? "$-"
146147
: props.query.data
147-
? `$${props.query.data?.totalPayoutsUSD}`
148+
? toUSD(props.query.data.totalPayoutsUSD)
148149
: undefined
149150
}
150151
skeletonData="$20"

apps/dashboard/src/components/pay/PayAnalytics/components/TotalVolumePieChart.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { SkeletonContainer } from "@/components/ui/skeleton";
22
import { cn } from "@/lib/utils";
33
import { Cell, Pie, PieChart } from "recharts";
4+
import { toUSD } from "../../../../utils/number";
45
import { usePayVolume } from "../hooks/usePayVolume";
56
import { FailedToLoad, chartHeight } from "./common";
67

@@ -155,7 +156,7 @@ function RenderData(props: { query: ProcessedQuery }) {
155156
<SkeletonContainer
156157
loadedData={
157158
queryData
158-
? `$${queryData?.totalAmount.toLocaleString()}`
159+
? toUSD(queryData.totalAmount)
159160
: props.query.isEmpty
160161
? "NA"
161162
: undefined
@@ -187,7 +188,7 @@ function RenderData(props: { query: ProcessedQuery }) {
187188
label={v.name}
188189
amount={
189190
queryData
190-
? `$${v.amount.toLocaleString()}`
191+
? toUSD(v.amount)
191192
: props.query.isEmpty
192193
? "$-"
193194
: undefined

apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/TotalSponsoredChartCard.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { useMemo } from "react";
2424
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
2525
import type { UserOpStats } from "types/analytics";
2626
import { useAllChainsData } from "../../../hooks/chains/allChains";
27-
import { formatTickerNumber } from "../../../lib/format-utils";
27+
import { toUSD } from "../../../utils/number";
2828

2929
type ChartData = Record<string, number> & {
3030
time: string; // human readable date
@@ -109,6 +109,8 @@ export function TotalSponsoredChartCard(props: {
109109
chartData.length === 0 ||
110110
chartData.every((data) => data.sponsoredUsd === 0);
111111

112+
console.log("chatConfig", chartConfig);
113+
112114
return (
113115
<div className="relative w-full rounded-lg border border-border bg-card p-4 md:p-6">
114116
<h3 className="mb-1 font-semibold text-xl tracking-tight md:text-2xl">
@@ -210,13 +212,22 @@ export function TotalSponsoredChartCard(props: {
210212
}}
211213
tickLine={false}
212214
axisLine={false}
213-
tickFormatter={(value) => `$${formatTickerNumber(value)}`}
215+
tickFormatter={(value) => toUSD(value)}
214216
/>
215217

216218
<ChartTooltip
217219
cursor={true}
218220
content={
219-
<ChartTooltipContent valueFormatter={(value) => `$${value}`} />
221+
<ChartTooltipContent
222+
valueFormatter={(value) => {
223+
// typeguard
224+
if (typeof value !== "number") {
225+
return "";
226+
}
227+
228+
return toUSD(value);
229+
}}
230+
/>
220231
}
221232
/>
222233
<ChartLegend content={<ChartLegendContent />} />

apps/dashboard/src/components/smart-wallets/AccountAbstractionAnalytics/storyUtils.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ export function createUserOpStatsStub(days: number): UserOpStats[] {
55

66
let d = days;
77
while (d !== 0) {
8-
const successful = Math.floor(Math.random() * 100);
9-
const failed = Math.floor(Math.random() * 100);
10-
const sponsoredUsd = Math.floor(Math.random() * 100);
8+
// don't use Math.floor because real data doesn't not have integer values
9+
const successful = Math.random() * 100;
10+
const failed = Math.random() * 100;
11+
const sponsoredUsd = Math.random() * 100;
12+
1113
stubbedData.push({
1214
date: new Date(2024, 1, d).toLocaleString(),
1315
successful,

apps/dashboard/src/lib/format-utils.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
1+
const compactNumberFormatter = new Intl.NumberFormat("en-US", {
2+
notation: "compact",
3+
});
4+
15
export const formatTickerNumber = (value: number) => {
2-
if (value >= 1000000) {
3-
const millions = value / 1000000;
4-
// Only show decimal if not a whole number, up to 2 decimals with no trailing zeros
5-
return `${millions % 1 === 0 ? millions.toFixed(0) : Number(millions.toFixed(2)).toString()}M`;
6-
}
7-
if (value >= 1000) {
8-
const thousands = value / 1000;
9-
// Only show decimal if not a whole number
10-
return `${thousands % 1 === 0 ? thousands.toFixed(0) : thousands.toFixed(1)}k`;
11-
}
12-
return value.toString();
6+
return compactNumberFormatter.format(value);
137
};
148

159
export const formatWalletType = (walletType: string) => {

apps/dashboard/src/utils/number.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
const usdCurrencyFormatter = new Intl.NumberFormat("en-US", {
2+
style: "currency",
3+
currency: "USD", // prefix with $
4+
minimumFractionDigits: 0, // don't show decimal places if value is a whole number
5+
maximumFractionDigits: 2, // at max 2 decimal places
6+
roundingMode: "halfEven", // round to nearest even number, standard practice for financial calculations
7+
notation: "compact", // shows 1.2M instead of 1,200,000, 1.2B instead of 1,200,000,000
8+
});
9+
110
export const toUSD = (value: number) => {
2-
return new Intl.NumberFormat(undefined, {
3-
style: "currency",
4-
currency: "USD",
5-
}).format(value);
11+
return usdCurrencyFormatter.format(value);
612
};
713

814
export const toSize = (value: number | bigint, defaultUnit?: string) => {

0 commit comments

Comments
 (0)