diff --git a/frontend/src/components/Leaderboard.jsx b/frontend/src/components/Leaderboard.jsx index 2e8569bd..c3f59282 100644 --- a/frontend/src/components/Leaderboard.jsx +++ b/frontend/src/components/Leaderboard.jsx @@ -1,6 +1,6 @@ import { useEffect, useState, useCallback } from 'react'; import { CONTRACT_ADDRESS, CONTRACT_NAME } from '../config/contracts'; -import { formatSTX } from '../lib/utils'; +import { formatSTX, formatAddress } from '../lib/utils'; import CopyButton from './ui/copy-button'; const API_BASE = 'https://api.hiro.so'; @@ -72,7 +72,7 @@ export default function Leaderboard() { return b.totalReceived - a.totalReceived; }).slice(0, 20); - const truncateAddress = (addr) => `${addr.slice(0, 8)}...${addr.slice(-6)}`; + const truncateAddress = (addr) => formatAddress(addr, 8, 6); if (loading) { return ( diff --git a/frontend/src/components/NotificationBell.jsx b/frontend/src/components/NotificationBell.jsx index b0a6133b..4a5031e1 100644 --- a/frontend/src/components/NotificationBell.jsx +++ b/frontend/src/components/NotificationBell.jsx @@ -1,5 +1,5 @@ import { useState, useRef, useEffect } from 'react'; -import { formatSTX } from '../lib/utils'; +import { formatSTX, formatAddress } from '../lib/utils'; export default function NotificationBell({ notifications, unreadCount, onMarkRead, loading }) { const [open, setOpen] = useState(false); @@ -22,8 +22,7 @@ export default function NotificationBell({ notifications, unreadCount, onMarkRea } }; - const truncateAddr = (addr) => - addr ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : ''; + const truncateAddr = (addr) => formatAddress(addr, 6, 4); return (
- Total Volume: {(stats['total-sent'].value / 1000000).toFixed(2)} STX + Total Volume: {formatSTX(stats['total-sent'].value, 2)} STX
- Total Earned: {(stats['total-received'].value / 1000000).toFixed(2)} STX + Total Earned: {formatSTX(stats['total-received'].value, 2)} STX
diff --git a/frontend/src/lib/utils.js b/frontend/src/lib/utils.js index 7e08a0e0..8d45d25a 100644 --- a/frontend/src/lib/utils.js +++ b/frontend/src/lib/utils.js @@ -26,3 +26,25 @@ export function formatSTX(microStx, decimals = 6) { export function toMicroSTX(stx) { return Math.floor(parseFloat(stx) * MICRO_STX); } + +/** + * Truncate a Stacks address for display. + * @param {string} address - Full address + * @param {number} [startChars=6] - Characters to show from start + * @param {number} [endChars=4] - Characters to show from end + * @returns {string} Truncated address + */ +export function formatAddress(address, startChars = 6, endChars = 4) { + if (!address || address.length <= startChars + endChars + 3) return address || ''; + return `${address.slice(0, startChars)}...${address.slice(-endChars)}`; +} + +/** + * Locale-aware number formatting. + * @param {number|string} n - Number to format + * @param {object} [options] - Intl.NumberFormat options + * @returns {string} Formatted number + */ +export function formatNumber(n, options = {}) { + return Number(n).toLocaleString(undefined, options); +} diff --git a/frontend/src/test/utils.test.js b/frontend/src/test/utils.test.js index 9441e5b2..de571d5d 100644 --- a/frontend/src/test/utils.test.js +++ b/frontend/src/test/utils.test.js @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { formatSTX, toMicroSTX, cn } from '../lib/utils'; +import { formatSTX, toMicroSTX, cn, formatAddress, formatNumber } from '../lib/utils'; describe('formatSTX', () => { it('converts micro-STX to STX string', () => { @@ -59,3 +59,48 @@ describe('cn', () => { expect(result).not.toContain('hidden'); }); }); + +describe('formatAddress', () => { + it('truncates a standard Stacks address', () => { + const addr = 'SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T'; + expect(formatAddress(addr)).toBe('SP31PK...2W5T'); + }); + + it('uses custom start and end lengths', () => { + const addr = 'SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T'; + expect(formatAddress(addr, 8, 6)).toBe('SP31PKQV...VS2W5T'); + }); + + it('returns short addresses unmodified', () => { + expect(formatAddress('SP123')).toBe('SP123'); + }); + + it('handles empty or null input', () => { + expect(formatAddress('')).toBe(''); + expect(formatAddress(null)).toBe(''); + expect(formatAddress(undefined)).toBe(''); + }); +}); + +describe('formatNumber', () => { + it('formats a number with locale separators', () => { + const result = formatNumber(1234567); + expect(result).toContain('1'); + expect(result.length).toBeGreaterThan(3); + }); + + it('formats with decimal options', () => { + const result = formatNumber(1234.5, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + expect(result).toContain('34'); + expect(result).toContain('50'); + }); + + it('handles zero', () => { + expect(formatNumber(0)).toBe('0'); + }); + + it('handles string input', () => { + const result = formatNumber('999'); + expect(result).toContain('999'); + }); +});