From 0d08ba807bc2d04ab0bf2159bf81fe3d494321e1 Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Tue, 16 Apr 2024 12:20:29 +0200 Subject: [PATCH] better number formatting (#9) * tweak number display * select whole amount upon input focus * handle letter input * number formatting --- features/prices/components/Price/Price.tsx | 8 +++++-- .../prices/components/Price/PriceChange.tsx | 11 ++------- features/prices/hooks/index.tsx | 1 + features/prices/hooks/use-locale.ts | 16 +++++++++++++ features/prices/hooks/use-prices.tsx | 2 +- features/strategies/components/Buy.tsx | 4 ++-- features/strategies/components/FormAmount.tsx | 7 ++++++ .../strategies/components/Result/Result.tsx | 23 +++++++++++-------- .../strategies/components/Swap/Results.tsx | 2 +- features/strategies/hooks/use-quote.ts | 2 +- lib/fetch.ts | 5 ++++ lib/index.ts | 3 +++ lib/numbers.ts | 22 ++++++++++++++++++ lib/{utils.ts => tokens.ts} | 14 ----------- 14 files changed, 81 insertions(+), 39 deletions(-) create mode 100644 features/prices/hooks/use-locale.ts create mode 100644 lib/fetch.ts create mode 100644 lib/index.ts create mode 100644 lib/numbers.ts rename lib/{utils.ts => tokens.ts} (50%) diff --git a/features/prices/components/Price/Price.tsx b/features/prices/components/Price/Price.tsx index ead9b7f..400ed8d 100644 --- a/features/prices/components/Price/Price.tsx +++ b/features/prices/components/Price/Price.tsx @@ -1,9 +1,11 @@ -import { usePrices, type PriceCoingecko } from '@/features/prices' +import { useLocale, usePrices, type PriceCoingecko } from '@/features/prices' import { PriceChange } from './PriceChange' import styles from './Price.module.css' +import { formatFiat } from '@/lib' export function Price({ price }: { price: PriceCoingecko }) { const { isValidating, isLoading } = usePrices() + const locale = useLocale() const feedbackClasses = isLoading ? 'isLoading' @@ -13,7 +15,9 @@ export function Price({ price }: { price: PriceCoingecko }) { return (

- ${price.usd} + + {formatFiat(price.usd, 'USD', locale)} + {price?.usd_24h_change ? ( ) : null} diff --git a/features/prices/components/Price/PriceChange.tsx b/features/prices/components/Price/PriceChange.tsx index 65a4313..e7e2ee5 100644 --- a/features/prices/components/Price/PriceChange.tsx +++ b/features/prices/components/Price/PriceChange.tsx @@ -2,19 +2,12 @@ import { TriangleUpIcon, TriangleDownIcon } from '@radix-ui/react-icons' import styles from './PriceChange.module.css' -import { useEffect, useState } from 'react' +import { useLocale } from '@/features/prices/hooks/use-locale' export function PriceChange({ priceChange }: { priceChange: number }) { - const [locale, setLocale] = useState('en-US') + const locale = useLocale() const styleClasses = priceChange > 0 ? styles.positive : styles.negative - useEffect(() => { - const userLocale = navigator?.languages?.length - ? navigator.languages[0] - : navigator.language - setLocale(userLocale) - }, []) - return ( { + const userLocale = navigator?.languages?.length + ? navigator.languages[0] + : navigator.language + setLocale(userLocale) + }, []) + + return locale +} diff --git a/features/prices/hooks/use-prices.tsx b/features/prices/hooks/use-prices.tsx index a9b1823..4d333ae 100644 --- a/features/prices/hooks/use-prices.tsx +++ b/features/prices/hooks/use-prices.tsx @@ -1,7 +1,7 @@ 'use client' import { tokens } from '@/constants' -import { fetcher, getTokenAddressBySymbol } from '@/lib/utils' +import { fetcher, getTokenAddressBySymbol } from '@/lib' import useSWR from 'swr' const tokenAddresses = tokens.map((token) => token.address).toString() diff --git a/features/strategies/components/Buy.tsx b/features/strategies/components/Buy.tsx index 243c600..bf8553e 100644 --- a/features/strategies/components/Buy.tsx +++ b/features/strategies/components/Buy.tsx @@ -4,8 +4,8 @@ import { useState } from 'react' import { useDebounce } from 'use-debounce' import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants' import { usePrices } from '@/features/prices' -import { getTokenBySymbol } from '@/lib/utils' -import { FormAmount, Result } from '@/features/strategies/components' +import { getTokenBySymbol } from '@/lib' +import { FormAmount, Result } from '@/features/strategies' import stylesShared from '@/features/strategies/styles/shared.module.css' export function Buy() { diff --git a/features/strategies/components/FormAmount.tsx b/features/strategies/components/FormAmount.tsx index 14dc583..61d8c87 100644 --- a/features/strategies/components/FormAmount.tsx +++ b/features/strategies/components/FormAmount.tsx @@ -20,6 +20,8 @@ export function FormAmount({ if (value === '') { setAmount(0) + } else if (isNaN(Number(value))) { + return } else { setAmount(Number(value)) } @@ -30,6 +32,10 @@ export function FormAmount({ setToken(e.target.value as TokenSymbol) } + function handleFocus(e: React.FocusEvent) { + e.target.select() + } + const options = isFiat ? [{ value: 'USD', label: 'USD' }] : [ @@ -46,6 +52,7 @@ export function FormAmount({ pattern="[0-9]*" value={amount} onChange={handleAmountChange} + onFocus={handleFocus} style={{ width: amount.toString().length + 'ch' }} /> diff --git a/features/strategies/components/Result/Result.tsx b/features/strategies/components/Result/Result.tsx index 1e75173..35fa392 100644 --- a/features/strategies/components/Result/Result.tsx +++ b/features/strategies/components/Result/Result.tsx @@ -1,8 +1,9 @@ import styles from './Result.module.css' -import { formatNumber } from '@/lib/utils' +import { formatCrypto, formatFiat } from '@/lib' import { ArrowRightIcon } from '@radix-ui/react-icons' import { TokenLogo } from '@/components' import { Token } from '@/types' +import { useLocale } from '@/features/prices' type Props = { token: Token | undefined @@ -23,11 +24,12 @@ export function Result({ isValidating, isLoading }: Props) { + const locale = useLocale() const feedbackClasses = isLoading ? 'isLoading' : isValidating - ? 'isValidating' - : '' + ? 'isValidating' + : '' return (

@@ -35,15 +37,18 @@ export function Result({

- - {formatNumber(amount || 0, token?.symbol || '')} + + {formatCrypto(amount || 0, token?.symbol || '', locale)}

{amountOriginalFiat ? (

- {formatNumber(amountOriginalFiat || 0, 'USD')} + {formatFiat(amountOriginalFiat || 0, 'USD', locale)}

) : null} @@ -53,13 +58,13 @@ export function Result({

- - {formatNumber(amountAsi || 0, 'ASI')} + + {formatCrypto(amountAsi || 0, 'ASI', locale)}

- {formatNumber(amountFiat || 0, 'USD')} + {formatFiat(amountFiat || 0, 'USD', locale)}

diff --git a/features/strategies/components/Swap/Results.tsx b/features/strategies/components/Swap/Results.tsx index 8202f58..5e61639 100644 --- a/features/strategies/components/Swap/Results.tsx +++ b/features/strategies/components/Swap/Results.tsx @@ -1,5 +1,5 @@ import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants' -import { getTokenBySymbol } from '@/lib/utils' +import { getTokenBySymbol } from '@/lib' import { type TokenSymbol } from '@/types' import { usePrices, type Prices } from '@/features/prices' import { type Market, useQuote } from '@/features/strategies' diff --git a/features/strategies/hooks/use-quote.ts b/features/strategies/hooks/use-quote.ts index 91c7700..b45bfc7 100644 --- a/features/strategies/hooks/use-quote.ts +++ b/features/strategies/hooks/use-quote.ts @@ -1,7 +1,7 @@ 'use client' import { TokenSymbol } from '@/types' -import { getTokenAddressBySymbol, fetcher } from '@/lib/utils' +import { getTokenAddressBySymbol, fetcher } from '@/lib' import useSWR from 'swr' const options = { diff --git a/lib/fetch.ts b/lib/fetch.ts new file mode 100644 index 0000000..21f956c --- /dev/null +++ b/lib/fetch.ts @@ -0,0 +1,5 @@ +export async function fetcher(url: string) { + const res = await fetch(url) + if (!res.ok) throw new Error('Failed to fetch') + return await res.json() +} diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..56b0f5d --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,3 @@ +export * from './numbers' +export * from './fetch' +export * from './tokens' diff --git a/lib/numbers.ts b/lib/numbers.ts new file mode 100644 index 0000000..92bbe6d --- /dev/null +++ b/lib/numbers.ts @@ -0,0 +1,22 @@ +import { formatCurrency } from '@coingecko/cryptoformat' + +export function formatCrypto(price: number, currency: string, locale: string) { + return formatCurrency(price, currency, locale, false, { + decimalPlaces: 3, + significantFigures: 1 + }) +} + +export function formatFiat(price: number, currency: string, locale: string) { + let formattedPrice = formatCurrency(price, currency, locale, false, { + decimalPlaces: 2, + significantFigures: 8 + }) + + // Add a trailing zero if only one digit after the decimal + if (formattedPrice.includes('.') && formattedPrice.split('.')[1].length < 2) { + formattedPrice += '0' + } + + return formattedPrice +} diff --git a/lib/utils.ts b/lib/tokens.ts similarity index 50% rename from lib/utils.ts rename to lib/tokens.ts index 416d50a..655282c 100644 --- a/lib/utils.ts +++ b/lib/tokens.ts @@ -1,19 +1,5 @@ import { tokens } from '@/constants' import type { TokenAddress, Token } from '@/types' -import { formatCurrency } from '@coingecko/cryptoformat' - -export function formatNumber(price: number, currency: string) { - return formatCurrency(price, currency, 'en', false, { - decimalPlaces: 3, - significantFigures: 5 - }) -} - -export async function fetcher(url: string) { - const res = await fetch(url) - if (!res.ok) throw new Error('Failed to fetch') - return await res.json() -} export function getTokenBySymbol(symbol: string): Token | undefined { const token = tokens.find((t) => t.symbol === symbol)