mirror of
https://github.com/kremalicious/asi-calculator.git
synced 2024-12-22 09:23:16 +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 { PriceChange } from './PriceChange'
|
||||||
import styles from './Price.module.css'
|
import styles from './Price.module.css'
|
||||||
|
import { formatFiat } from '@/lib'
|
||||||
|
|
||||||
export function Price({ price }: { price: PriceCoingecko }) {
|
export function Price({ price }: { price: PriceCoingecko }) {
|
||||||
const { isValidating, isLoading } = usePrices()
|
const { isValidating, isLoading } = usePrices()
|
||||||
|
const locale = useLocale()
|
||||||
|
|
||||||
const feedbackClasses = isLoading
|
const feedbackClasses = isLoading
|
||||||
? 'isLoading'
|
? 'isLoading'
|
||||||
@ -13,7 +15,9 @@ export function Price({ price }: { price: PriceCoingecko }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<p className={styles.price}>
|
<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 ? (
|
{price?.usd_24h_change ? (
|
||||||
<PriceChange priceChange={price.usd_24h_change} />
|
<PriceChange priceChange={price.usd_24h_change} />
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -2,19 +2,12 @@
|
|||||||
|
|
||||||
import { TriangleUpIcon, TriangleDownIcon } from '@radix-ui/react-icons'
|
import { TriangleUpIcon, TriangleDownIcon } from '@radix-ui/react-icons'
|
||||||
import styles from './PriceChange.module.css'
|
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 }) {
|
export function PriceChange({ priceChange }: { priceChange: number }) {
|
||||||
const [locale, setLocale] = useState('en-US')
|
const locale = useLocale()
|
||||||
const styleClasses = priceChange > 0 ? styles.positive : styles.negative
|
const styleClasses = priceChange > 0 ? styles.positive : styles.negative
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const userLocale = navigator?.languages?.length
|
|
||||||
? navigator.languages[0]
|
|
||||||
: navigator.language
|
|
||||||
setLocale(userLocale)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`${styles.change} ${styleClasses}`}
|
className={`${styles.change} ${styleClasses}`}
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export * from './use-prices'
|
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'
|
'use client'
|
||||||
|
|
||||||
import { tokens } from '@/constants'
|
import { tokens } from '@/constants'
|
||||||
import { fetcher, getTokenAddressBySymbol } from '@/lib/utils'
|
import { fetcher, getTokenAddressBySymbol } from '@/lib'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
const tokenAddresses = tokens.map((token) => token.address).toString()
|
const tokenAddresses = tokens.map((token) => token.address).toString()
|
||||||
|
@ -4,8 +4,8 @@ import { useState } from 'react'
|
|||||||
import { useDebounce } from 'use-debounce'
|
import { useDebounce } from 'use-debounce'
|
||||||
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
||||||
import { usePrices } from '@/features/prices'
|
import { usePrices } from '@/features/prices'
|
||||||
import { getTokenBySymbol } from '@/lib/utils'
|
import { getTokenBySymbol } from '@/lib'
|
||||||
import { FormAmount, Result } from '@/features/strategies/components'
|
import { FormAmount, Result } from '@/features/strategies'
|
||||||
import stylesShared from '@/features/strategies/styles/shared.module.css'
|
import stylesShared from '@/features/strategies/styles/shared.module.css'
|
||||||
|
|
||||||
export function Buy() {
|
export function Buy() {
|
||||||
|
@ -20,6 +20,8 @@ export function FormAmount({
|
|||||||
|
|
||||||
if (value === '') {
|
if (value === '') {
|
||||||
setAmount(0)
|
setAmount(0)
|
||||||
|
} else if (isNaN(Number(value))) {
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
setAmount(Number(value))
|
setAmount(Number(value))
|
||||||
}
|
}
|
||||||
@ -30,6 +32,10 @@ export function FormAmount({
|
|||||||
setToken(e.target.value as TokenSymbol)
|
setToken(e.target.value as TokenSymbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleFocus(e: React.FocusEvent<HTMLInputElement>) {
|
||||||
|
e.target.select()
|
||||||
|
}
|
||||||
|
|
||||||
const options = isFiat
|
const options = isFiat
|
||||||
? [{ value: 'USD', label: 'USD' }]
|
? [{ value: 'USD', label: 'USD' }]
|
||||||
: [
|
: [
|
||||||
@ -46,6 +52,7 @@ export function FormAmount({
|
|||||||
pattern="[0-9]*"
|
pattern="[0-9]*"
|
||||||
value={amount}
|
value={amount}
|
||||||
onChange={handleAmountChange}
|
onChange={handleAmountChange}
|
||||||
|
onFocus={handleFocus}
|
||||||
style={{ width: amount.toString().length + 'ch' }}
|
style={{ width: amount.toString().length + 'ch' }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import styles from './Result.module.css'
|
import styles from './Result.module.css'
|
||||||
import { formatNumber } from '@/lib/utils'
|
import { formatCrypto, formatFiat } from '@/lib'
|
||||||
import { ArrowRightIcon } from '@radix-ui/react-icons'
|
import { ArrowRightIcon } from '@radix-ui/react-icons'
|
||||||
import { TokenLogo } from '@/components'
|
import { TokenLogo } from '@/components'
|
||||||
import { Token } from '@/types'
|
import { Token } from '@/types'
|
||||||
|
import { useLocale } from '@/features/prices'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
token: Token | undefined
|
token: Token | undefined
|
||||||
@ -23,11 +24,12 @@ export function Result({
|
|||||||
isValidating,
|
isValidating,
|
||||||
isLoading
|
isLoading
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const locale = useLocale()
|
||||||
const feedbackClasses = isLoading
|
const feedbackClasses = isLoading
|
||||||
? 'isLoading'
|
? 'isLoading'
|
||||||
: isValidating
|
: isValidating
|
||||||
? 'isValidating'
|
? 'isValidating'
|
||||||
: ''
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.result}>
|
<div className={styles.result}>
|
||||||
@ -35,15 +37,18 @@ export function Result({
|
|||||||
<TokenLogo token={token} />
|
<TokenLogo token={token} />
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<span className={feedbackClasses}>
|
<span
|
||||||
{formatNumber(amount || 0, token?.symbol || '')}
|
className={feedbackClasses}
|
||||||
|
title={`${amount} ${token?.symbol}`}
|
||||||
|
>
|
||||||
|
{formatCrypto(amount || 0, token?.symbol || '', locale)}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{amountOriginalFiat ? (
|
{amountOriginalFiat ? (
|
||||||
<p>
|
<p>
|
||||||
<span className={`${styles.fiat} ${feedbackClasses}`}>
|
<span className={`${styles.fiat} ${feedbackClasses}`}>
|
||||||
{formatNumber(amountOriginalFiat || 0, 'USD')}
|
{formatFiat(amountOriginalFiat || 0, 'USD', locale)}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
@ -53,13 +58,13 @@ export function Result({
|
|||||||
<ArrowRightIcon className={styles.iconArrow} />
|
<ArrowRightIcon className={styles.iconArrow} />
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<strong title={`${amountAsi}`} className={feedbackClasses}>
|
<strong title={`${amountAsi} ASI`} className={feedbackClasses}>
|
||||||
{formatNumber(amountAsi || 0, 'ASI')}
|
{formatCrypto(amountAsi || 0, 'ASI', locale)}
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong className={`${styles.fiat} ${feedbackClasses}`}>
|
<strong className={`${styles.fiat} ${feedbackClasses}`}>
|
||||||
{formatNumber(amountFiat || 0, 'USD')}
|
{formatFiat(amountFiat || 0, 'USD', locale)}
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants'
|
||||||
import { getTokenBySymbol } from '@/lib/utils'
|
import { getTokenBySymbol } from '@/lib'
|
||||||
import { type TokenSymbol } from '@/types'
|
import { type TokenSymbol } from '@/types'
|
||||||
import { usePrices, type Prices } from '@/features/prices'
|
import { usePrices, type Prices } from '@/features/prices'
|
||||||
import { type Market, useQuote } from '@/features/strategies'
|
import { type Market, useQuote } from '@/features/strategies'
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { TokenSymbol } from '@/types'
|
import { TokenSymbol } from '@/types'
|
||||||
import { getTokenAddressBySymbol, fetcher } from '@/lib/utils'
|
import { getTokenAddressBySymbol, fetcher } from '@/lib'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
|
|
||||||
const options = {
|
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 { tokens } from '@/constants'
|
||||||
import type { TokenAddress, Token } from '@/types'
|
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 {
|
export function getTokenBySymbol(symbol: string): Token | undefined {
|
||||||
const token = tokens.find((t) => t.symbol === symbol)
|
const token = tokens.find((t) => t.symbol === symbol)
|
Loading…
Reference in New Issue
Block a user