mirror of
https://github.com/kremalicious/asi-calculator.git
synced 2024-12-22 01:13:17 +01:00
better number formatting (#9)
* tweak number display * select whole amount upon input focus * handle letter input * number formatting
This commit is contained in:
parent
34464d00aa
commit
0d08ba807b
@ -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 (
|
||||
<p className={styles.price}>
|
||||
<span className={`${styles.fiat} ${feedbackClasses}`}>${price.usd}</span>
|
||||
<span className={`${styles.fiat} ${feedbackClasses}`}>
|
||||
{formatFiat(price.usd, 'USD', locale)}
|
||||
</span>
|
||||
{price?.usd_24h_change ? (
|
||||
<PriceChange priceChange={price.usd_24h_change} />
|
||||
) : null}
|
||||
|
@ -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 (
|
||||
<span
|
||||
className={`${styles.change} ${styleClasses}`}
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './use-prices'
|
||||
export * from './use-locale'
|
||||
|
16
features/prices/hooks/use-locale.ts
Normal file
16
features/prices/hooks/use-locale.ts
Normal file
@ -0,0 +1,16 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export function useLocale() {
|
||||
const [locale, setLocale] = useState('en-US')
|
||||
|
||||
useEffect(() => {
|
||||
const userLocale = navigator?.languages?.length
|
||||
? navigator.languages[0]
|
||||
: navigator.language
|
||||
setLocale(userLocale)
|
||||
}, [])
|
||||
|
||||
return locale
|
||||
}
|
@ -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()
|
||||
|
@ -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() {
|
||||
|
@ -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<HTMLInputElement>) {
|
||||
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' }}
|
||||
/>
|
||||
|
||||
|
@ -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 (
|
||||
<div className={styles.result}>
|
||||
@ -35,15 +37,18 @@ export function Result({
|
||||
<TokenLogo token={token} />
|
||||
|
||||
<p>
|
||||
<span className={feedbackClasses}>
|
||||
{formatNumber(amount || 0, token?.symbol || '')}
|
||||
<span
|
||||
className={feedbackClasses}
|
||||
title={`${amount} ${token?.symbol}`}
|
||||
>
|
||||
{formatCrypto(amount || 0, token?.symbol || '', locale)}
|
||||
</span>
|
||||
</p>
|
||||
|
||||
{amountOriginalFiat ? (
|
||||
<p>
|
||||
<span className={`${styles.fiat} ${feedbackClasses}`}>
|
||||
{formatNumber(amountOriginalFiat || 0, 'USD')}
|
||||
{formatFiat(amountOriginalFiat || 0, 'USD', locale)}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
@ -53,13 +58,13 @@ export function Result({
|
||||
<ArrowRightIcon className={styles.iconArrow} />
|
||||
|
||||
<p>
|
||||
<strong title={`${amountAsi}`} className={feedbackClasses}>
|
||||
{formatNumber(amountAsi || 0, 'ASI')}
|
||||
<strong title={`${amountAsi} ASI`} className={feedbackClasses}>
|
||||
{formatCrypto(amountAsi || 0, 'ASI', locale)}
|
||||
</strong>
|
||||
</p>
|
||||
<p>
|
||||
<strong className={`${styles.fiat} ${feedbackClasses}`}>
|
||||
{formatNumber(amountFiat || 0, 'USD')}
|
||||
{formatFiat(amountFiat || 0, 'USD', locale)}
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -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'
|
||||
|
@ -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 = {
|
||||
|
5
lib/fetch.ts
Normal file
5
lib/fetch.ts
Normal file
@ -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()
|
||||
}
|
3
lib/index.ts
Normal file
3
lib/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './numbers'
|
||||
export * from './fetch'
|
||||
export * from './tokens'
|
22
lib/numbers.ts
Normal file
22
lib/numbers.ts
Normal file
@ -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
|
||||
}
|
@ -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)
|
Loading…
Reference in New Issue
Block a user