Skip to content

Commit cbd0bb6

Browse files
wallet and explorer: expand precision formatting
1 parent bb1c193 commit cbd0bb6

16 files changed

Lines changed: 159 additions & 61 deletions

File tree

cmd/rpc/web/explorer/src/components/account/AccountDetailHeader.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { motion } from 'framer-motion'
33
import { Check, Copy } from 'lucide-react'
44
import AnimatedNumber from '../AnimatedNumber'
55
import accountDetailTexts from '../../data/accountDetail.json'
6-
import { toCNPY } from '../../lib/utils'
6+
import { cnpyDetailFormat, toCNPY } from '../../lib/utils'
77

88
interface Account {
99
address: string
@@ -86,10 +86,7 @@ const AccountDetailHeader: React.FC<AccountDetailHeaderProps> = ({ account }) =>
8686
<p className="text-sm text-white">
8787
<AnimatedNumber
8888
value={toCNPY(account.amount)}
89-
format={{
90-
minimumFractionDigits: 2,
91-
maximumFractionDigits: 2
92-
}}
89+
format={cnpyDetailFormat}
9390
className="text-white"
9491
/>
9592
<span className="ml-2 text-white">CNPY</span>

cmd/rpc/web/explorer/src/components/account/AccountTransactionsTable.tsx

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import accountDetailTexts from '../../data/accountDetail.json'
55
import transactionsTexts from '../../data/transactions.json'
66
import AnimatedNumber from '../AnimatedNumber'
77
import TransactionTypeBadge from '../transaction/TransactionTypeBadge'
8-
import { formatPaginationRange, isRowNavigationKey, shouldIgnoreRowNavigation } from '../../lib/utils'
8+
import { cnpyDetailFormat, formatMicroCNPY, formatPaginationRange, isRowNavigationKey, shouldIgnoreRowNavigation, toCNPY } from '../../lib/utils'
99
import PageSizeSelect from '../shared/PageSizeSelect'
1010
import CopyableIdentifier from '../ui/CopyableIdentifier'
1111

@@ -89,18 +89,6 @@ const AccountTransactionsTable: React.FC<AccountTransactionsTableProps> = ({
8989
}
9090
}
9191

92-
// Helper function to convert micro denomination to CNPY
93-
const toCNPY = (micro: number): number => {
94-
return micro / 1000000
95-
}
96-
97-
const formatFee = (fee: number) => {
98-
if (!fee || fee === 0) return '0'
99-
// Fee comes in micro denomination from endpoint, convert to CNPY
100-
const cnpy = toCNPY(fee)
101-
return cnpy.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 })
102-
}
103-
10492
const normalizeType = (type: string): string => {
10593
const typeLower = type.toLowerCase()
10694
// Normalize editStake variations
@@ -191,7 +179,7 @@ const AccountTransactionsTable: React.FC<AccountTransactionsTableProps> = ({
191179
{typeof amountCNPY === 'number' && amountCNPY > 0 ? (
192180
<AnimatedNumber
193181
value={amountCNPY}
194-
format={{ maximumFractionDigits: 4 }}
182+
format={cnpyDetailFormat}
195183
className="text-white"
196184
/>
197185
) : (
@@ -201,7 +189,7 @@ const AccountTransactionsTable: React.FC<AccountTransactionsTableProps> = ({
201189

202190
// Fee (in micro denomination from endpoint) with minimum fee info
203191
<span key="fee" className="text-sm text-white tabular-nums">
204-
{typeof feeMicro === 'number' ? formatFee(feeMicro) : formatFee(feeMicro || 0)}
192+
{formatMicroCNPY(feeMicro || 0)}
205193
</span>,
206194

207195
// Block
@@ -379,7 +367,7 @@ const AccountTransactionsTable: React.FC<AccountTransactionsTableProps> = ({
379367
{typeof amountCNPY === 'number' && amountCNPY > 0 ? (
380368
<AnimatedNumber
381369
value={amountCNPY}
382-
format={{ maximumFractionDigits: 4 }}
370+
format={cnpyDetailFormat}
383371
className="text-white"
384372
/>
385373
) : (
@@ -389,7 +377,7 @@ const AccountTransactionsTable: React.FC<AccountTransactionsTableProps> = ({
389377
</div>
390378
<div className="flex items-center justify-between">
391379
<span className="text-gray-400">Fee:</span>
392-
<span className="text-gray-300">{formatFee(feeMicro)}</span>
380+
<span className="text-gray-300">{formatMicroCNPY(feeMicro || 0)}</span>
393381
</div>
394382
<div className="flex items-center justify-between">
395383
<span className="text-gray-400">Block:</span>

cmd/rpc/web/explorer/src/components/account/AccountsPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import AccountsTable from './AccountsTable'
44
import { useAccounts } from '../../hooks/useApi'
55
import accountsTexts from '../../data/accounts.json'
66
import ExplorerOverviewCards from '../ExplorerOverviewCards'
7+
import { toCNPY } from '../../lib/utils'
78

89
const LiveIndicator = () => (
910
<span className="inline-flex items-center gap-1 rounded-full bg-green-500/10 px-2 py-0.5 text-sm text-primary">
@@ -19,7 +20,7 @@ const AccountsPage: React.FC = () => {
1920
const { data: accountsData, isLoading, error } = useAccounts(currentPage, pageSize)
2021
const accounts = accountsData?.results || []
2122
const largestBalance = React.useMemo(
22-
() => accounts.reduce((max: number, account: { amount?: number }) => Math.max(max, Number(account.amount || 0)), 0),
23+
() => toCNPY(accounts.reduce((max: number, account: { amount?: number }) => Math.max(max, Number(account.amount || 0)), 0)),
2324
[accounts],
2425
)
2526
const overviewCards = [

cmd/rpc/web/explorer/src/components/account/AccountsTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react'
22
import { useNavigate } from 'react-router-dom'
33
import accountsTexts from '../../data/accounts.json'
44
import AnimatedNumber from '../AnimatedNumber'
5-
import { formatPaginationRange, isRowNavigationKey, shouldIgnoreRowNavigation } from '../../lib/utils'
5+
import { formatPaginationRange, isRowNavigationKey, shouldIgnoreRowNavigation, toCNPY } from '../../lib/utils'
66
import PageSizeSelect from '../shared/PageSizeSelect'
77
import CnpyColorIcon from '../ui/CnpyColorIcon'
88
import CopyableIdentifier from '../ui/CopyableIdentifier'
@@ -142,7 +142,7 @@ const AccountsTable: React.FC<AccountsTableProps> = ({
142142
style={{ borderTopRightRadius: '10px', borderBottomRightRadius: '10px' }}
143143
>
144144
<span className="text-sm text-white tabular-nums">
145-
<AnimatedNumber value={account.amount} format={{ maximumFractionDigits: 4 }} className="text-white" />
145+
<AnimatedNumber value={toCNPY(account.amount)} format={{ maximumFractionDigits: 4 }} className="text-white" />
146146
<span className="ml-1 text-white/50">CNPY</span>
147147
</span>
148148
</td>

cmd/rpc/web/explorer/src/components/block/BlockDetailInfo.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Copy } from 'lucide-react'
44
import toast from 'react-hot-toast'
55
import blockDetailTexts from '../../data/blockDetail.json'
66
import { GREEN_BADGE_CLASS } from '../ui/badgeStyles'
7+
import { formatCNPY } from '../../lib/utils'
78

89
interface BlockDetailInfoProps {
910
block: {
@@ -147,7 +148,7 @@ const BlockDetailInfo: React.FC<BlockDetailInfoProps> = ({ block, blockData }) =
147148
</div>
148149
<div className="flex flex-wrap justify-between items-center border-b border-gray-400/30 pb-4">
149150
<span className="text-gray-400 mr-2">{blockDetailTexts.blockDetails.fields.blockReward}</span>
150-
<span className="text-primary">{block.blockReward} {blockDetailTexts.blockDetails.units.cnpy}</span>
151+
<span className="text-primary">{formatCNPY(block.blockReward)} {blockDetailTexts.blockDetails.units.cnpy}</span>
151152
</div>
152153

153154
<div className="flex flex-wrap justify-between items-center border-b border-gray-400/30 pb-4">
@@ -157,7 +158,7 @@ const BlockDetailInfo: React.FC<BlockDetailInfoProps> = ({ block, blockData }) =
157158

158159
<div className="flex flex-wrap justify-between items-center border-b border-gray-400/30 pb-4">
159160
<span className="text-gray-400 mr-2">{blockDetailTexts.blockDetails.fields.totalTransactionFees}</span>
160-
<span className="text-orange-400">{block.totalTransactionFees} {blockDetailTexts.blockDetails.units.cnpy}</span>
161+
<span className="text-orange-400">{formatCNPY(block.totalTransactionFees)} {blockDetailTexts.blockDetails.units.cnpy}</span>
161162
</div>
162163

163164
</div>

cmd/rpc/web/explorer/src/components/block/BlockTransactions.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import blockDetailTexts from '../../data/blockDetail.json'
44
import transactionsTexts from '../../data/transactions.json'
55
import AnimatedNumber from '../AnimatedNumber'
66
import TransactionTypeBadge from '../transaction/TransactionTypeBadge'
7-
import { formatPaginationRange, isRowNavigationKey, shouldIgnoreRowNavigation } from '../../lib/utils'
7+
import { cnpyDetailFormat, formatMicroCNPY, formatPaginationRange, isRowNavigationKey, shouldIgnoreRowNavigation } from '../../lib/utils'
88
import { GREEN_BADGE_CLASS } from '../ui/badgeStyles'
99
import CopyableIdentifier from '../ui/CopyableIdentifier'
1010

@@ -51,12 +51,6 @@ const BlockTransactions: React.FC<BlockTransactionsProps> = ({
5151
return `${value.slice(0, leading)}${value.slice(-trailing)}`
5252
}
5353

54-
const formatFee = (fee: number) => {
55-
if (!fee || fee === 0) return '0 CNPY'
56-
const cnpy = fee / 1_000_000
57-
return `${cnpy.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 })} CNPY`
58-
}
59-
6054
const getTransactionType = (tx: Transaction): string => {
6155
return tx.type || tx.messageType || 'send'
6256
}
@@ -171,12 +165,12 @@ const BlockTransactions: React.FC<BlockTransactionsProps> = ({
171165
</td>
172166
<td className={desktopRowCellClass}>
173167
<span className="text-sm text-white tabular-nums">
174-
<AnimatedNumber value={amount} format={{ maximumFractionDigits: 4 }} className="text-white" />
168+
<AnimatedNumber value={amount} format={cnpyDetailFormat} className="text-white" />
175169
<span className="ml-1 text-white/50">{transactionsTexts.table.units.cnpy}</span>
176170
</span>
177171
</td>
178172
<td className={desktopRowCellClass}>
179-
<span className="text-sm text-white tabular-nums">{formatFee(tx.fee || 0)}</span>
173+
<span className="text-sm text-white tabular-nums">{formatMicroCNPY(tx.fee || 0)}</span>
180174
</td>
181175
<td className={desktopRowCellClass}>
182176
<span className={GREEN_BADGE_CLASS}>

cmd/rpc/web/explorer/src/components/search/SearchResults.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom'
44
import AnimatedNumber from '../AnimatedNumber'
55
import toast from 'react-hot-toast'
66
import { Account, TransactionsBySender, TransactionsByRec } from '../../lib/api'
7+
import { toCNPY } from '../../lib/utils'
78
import { GREEN_BADGE_CLASS } from '../ui/badgeStyles'
89

910
interface SearchResultsProps {
@@ -23,6 +24,12 @@ interface FieldConfig {
2324
fullWidth?: boolean
2425
}
2526

27+
const formatAccountBalance = (amount: number | undefined) =>
28+
toCNPY(Number(amount || 0)).toLocaleString(undefined, {
29+
minimumFractionDigits: 2,
30+
maximumFractionDigits: 2,
31+
})
32+
2633
const SearchResults: React.FC<SearchResultsProps> = ({ results, filters }) => {
2734
// Sync activeTab with filter.type if filter is set
2835
const initialTab = filters?.type !== 'all' ? filters.type : 'all'
@@ -234,7 +241,7 @@ const SearchResults: React.FC<SearchResultsProps> = ({ results, filters }) => {
234241
fetchAddressData()
235242
}, [address])
236243

237-
const balance = accountData?.amount ? (accountData.amount / 1000000).toFixed(2) : (initialData?.amount ? (initialData.amount / 1000000).toFixed(2) : '0.00')
244+
const balance = formatAccountBalance(accountData?.amount ?? initialData?.amount)
238245

239246
return (
240247
<motion.div

cmd/rpc/web/explorer/src/components/token-swaps/OrderDetailPage.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import { Copy } from 'lucide-react'
55
import { useOrder } from '../../hooks/useApi'
66
import toast from 'react-hot-toast'
77

8-
import { toCNPY } from '../../lib/utils'
8+
import { formatDecimalWithSubscript, formatLocaleNumber, formatMicroCNPY } from '../../lib/utils'
99
import { GREEN_BADGE_CLASS } from '../ui/badgeStyles'
1010

11+
const SWAP_DECIMAL_PLACES = 6
12+
1113
const formatAmount = (micro: number): string => {
12-
if (micro === 0) return '0 CNPY'
13-
const cnpy = toCNPY(micro)
14-
return `${cnpy.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 })} CNPY`
14+
return formatMicroCNPY(micro)
1515
}
1616

1717
const CopySymbol = () => <Copy aria-hidden="true" className="h-3.5 w-3.5" strokeWidth={2} />
@@ -101,7 +101,7 @@ const OrderDetailPage: React.FC = () => {
101101
const status: string = buyerSendAddress ? 'Locked' : 'Active'
102102

103103
const exchangeRate = requestedAmount > 0
104-
? (amountForSale / requestedAmount).toFixed(6)
104+
? formatDecimalWithSubscript(amountForSale / requestedAmount, SWAP_DECIMAL_PLACES, SWAP_DECIMAL_PLACES)
105105
: 'N/A'
106106

107107
return (

cmd/rpc/web/explorer/src/components/token-swaps/TokenSwapsPage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import SwapFilters from './SwapFilters';
55
import type { SwapFilterValues } from './SwapFilters';
66
import RecentSwapsTable from './RecentSwapsTable';
77
import { useOrders } from '../../hooks/useApi'
8-
import { toCNPY } from '../../lib/utils';
8+
import { formatDecimalWithSubscript, formatLocaleNumber, toCNPY } from '../../lib/utils';
99
import ExplorerOverviewCards from '../ExplorerOverviewCards';
1010

1111
interface Order {
@@ -38,6 +38,7 @@ export type SortKey = 'committee' | 'exchangeRate' | 'amount' | 'status';
3838
export type SortDir = 'asc' | 'desc';
3939

4040
const DEFAULT_FILTERS: SwapFilterValues = { minAmount: '', status: 'All', committee: 'All' };
41+
const SWAP_DECIMAL_PLACES = 2;
4142

4243
const TokenSwapsPage: React.FC = () => {
4344
const navigate = useNavigate();
@@ -68,12 +69,12 @@ const TokenSwapsPage: React.FC = () => {
6869
? order.amountForSale / order.requestedAmount
6970
: 0;
7071
const exchangeRate = exchangeRateNum > 0
71-
? `1 : ${exchangeRateNum.toFixed(6)}`
72+
? `1 : ${formatDecimalWithSubscript(exchangeRateNum, SWAP_DECIMAL_PLACES, SWAP_DECIMAL_PLACES)}`
7273
: 'N/A';
7374

7475
const status: 'Active' | 'Locked' = order.buyerSendAddress ? 'Locked' : 'Active';
7576
const amountRaw = toCNPY(order.amountForSale);
76-
const amount = `${amountRaw.toFixed(6)} CNPY`;
77+
const amount = `${formatLocaleNumber(amountRaw, SWAP_DECIMAL_PLACES, SWAP_DECIMAL_PLACES)} CNPY`;
7778

7879
return {
7980
orderId: order.id,

cmd/rpc/web/explorer/src/components/validator/ValidatorMetrics.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from 'react'
22
import { motion } from 'framer-motion'
33
import validatorDetailTexts from '../../data/validatorDetail.json'
44
import AnimatedNumber from '../AnimatedNumber'
5+
import { cnpyDetailFormat, toCNPY } from '../../lib/utils'
56

67
interface ValidatorDetail {
78
stakedAmount: number // in micro denomination
@@ -15,11 +16,6 @@ interface ValidatorMetricsProps {
1516
}
1617

1718
const ValidatorMetrics: React.FC<ValidatorMetricsProps> = ({ validator }) => {
18-
// Helper function to convert micro denomination to CNPY
19-
const toCNPY = (micro: number): number => {
20-
return micro / 1000000
21-
}
22-
2319
const stakedAmountCNPY = toCNPY(validator.stakedAmount)
2420

2521
// Format height display
@@ -36,27 +32,31 @@ const ValidatorMetrics: React.FC<ValidatorMetricsProps> = ({ validator }) => {
3632
suffix: ` ${validatorDetailTexts.metrics.units.cnpy}`,
3733
icon: 'fa-solid fa-lock',
3834
subtitle: null,
35+
format: cnpyDetailFormat,
3936
},
4037
{
4138
title: 'Committees Staked',
4239
value: validator.committees.length,
4340
suffix: '',
4441
icon: 'fa-solid fa-network-wired',
4542
subtitle: validator.committees.length > 0 ? `IDs: ${validator.committees.join(', ')}` : 'None',
43+
format: { maximumFractionDigits: 0, minimumFractionDigits: 0 },
4644
},
4745
{
4846
title: 'Max Paused Height',
4947
value: validator.maxPausedHeight > 0 ? validator.maxPausedHeight : 0,
5048
suffix: '',
5149
icon: 'fa-solid fa-pause-circle',
5250
subtitle: validator.maxPausedHeight > 0 ? `Height: ${formatHeight(validator.maxPausedHeight)}` : 'Not paused',
51+
format: { maximumFractionDigits: 0, minimumFractionDigits: 0 },
5352
},
5453
{
5554
title: 'Unstaking Height',
5655
value: validator.unstakingHeight > 0 ? validator.unstakingHeight : 0,
5756
suffix: '',
5857
icon: 'fa-solid fa-arrow-down',
5958
subtitle: validator.unstakingHeight > 0 ? `Height: ${formatHeight(validator.unstakingHeight)}` : 'Not unstaking',
59+
format: { maximumFractionDigits: 0, minimumFractionDigits: 0 },
6060
}
6161
]
6262

@@ -92,7 +92,7 @@ const ValidatorMetrics: React.FC<ValidatorMetricsProps> = ({ validator }) => {
9292
) : (
9393
<AnimatedNumber
9494
value={metric.value}
95-
format={{ maximumFractionDigits: 2 }}
95+
format={metric.format}
9696
className="text-white"
9797
/>
9898
)}

0 commit comments

Comments
 (0)