add halving to price carousel

This commit is contained in:
keyan 2024-04-16 17:58:26 -05:00
parent a0f3e338a8
commit 058f88da49
4 changed files with 92 additions and 29 deletions

View File

@ -2,13 +2,17 @@ import { createContext, useContext, useMemo } from 'react'
import { useQuery } from '@apollo/client' import { useQuery } from '@apollo/client'
import { NORMAL_POLL_INTERVAL, SSR } from '@/lib/constants' import { NORMAL_POLL_INTERVAL, SSR } from '@/lib/constants'
import { BLOCK_HEIGHT } from '@/fragments/blockHeight' import { BLOCK_HEIGHT } from '@/fragments/blockHeight'
import { datePivot } from '@/lib/time'
export const BlockHeightContext = createContext({ export const BlockHeightContext = createContext({
height: 0 height: 0,
halving: null
}) })
export const useBlockHeight = () => useContext(BlockHeightContext) export const useBlockHeight = () => useContext(BlockHeightContext)
const HALVING_INTERVAL = 210000
export const BlockHeightProvider = ({ blockHeight, children }) => { export const BlockHeightProvider = ({ blockHeight, children }) => {
const { data } = useQuery(BLOCK_HEIGHT, { const { data } = useQuery(BLOCK_HEIGHT, {
...(SSR ...(SSR
@ -18,9 +22,23 @@ export const BlockHeightProvider = ({ blockHeight, children }) => {
nextFetchPolicy: 'cache-and-network' nextFetchPolicy: 'cache-and-network'
}) })
}) })
const value = useMemo(() => ({ const value = useMemo(() => {
height: data?.blockHeight ?? blockHeight ?? 0 if (!data?.blockHeight) {
}), [data?.blockHeight, blockHeight]) return {
height: blockHeight ?? 0,
halving: null
}
}
const remainingBlocks = HALVING_INTERVAL - (data.blockHeight % HALVING_INTERVAL)
const minutesUntilHalving = remainingBlocks * 10
const halving = datePivot(new Date(), { minutes: minutesUntilHalving })
return {
height: data.blockHeight,
halving
}
}, [data?.blockHeight, blockHeight])
return ( return (
<BlockHeightContext.Provider value={value}> <BlockHeightContext.Provider value={value}>
{children} {children}

View File

@ -1,18 +1,53 @@
import Countdown from 'react-countdown' import Countdown from 'react-countdown'
export default function SimpleCountdown ({ className, onComplete, date }) { export default function SimpleCountdown (props) {
return ( return (
<span className={className}> <CountdownShared
<Countdown {...props} formatter={props => {
date={date} return (
renderer={props => <span className='text-monospace' suppressHydrationWarning> {props.formatted.minutes}:{props.formatted.seconds}</span>} <>
onComplete={onComplete} {props.formatted.minutes}:{props.formatted.seconds}
/> </>
</span> )
}}
/>
) )
} }
export function LongCountdown ({ className, onComplete, date }) { export function LongCountdown (props) {
return (
<CountdownShared
{...props} formatter={props => {
return (
<>
{props.formatted.days && `${props.formatted.days} days `}
{props.formatted.hours && `${props.formatted.hours} hours `}
{props.formatted.minutes && `${props.formatted.minutes} minutes `}
{props.formatted.seconds && `${props.formatted.seconds} seconds `}
</>
)
}}
/>
)
}
export function CompactLongCountdown (props) {
return (
<CountdownShared
{...props} formatter={props => {
return (
<>
{props.formatted.days
? ` ${props.formatted.days}d ${props.formatted.hours}h ${props.formatted.minutes}m ${props.formatted.seconds}s`
: ` ${props.formatted.hours}:${props.formatted.minutes}:${props.formatted.seconds}`}
</>
)
}}
/>
)
}
function CountdownShared ({ className, onComplete, date, formatter }) {
return ( return (
<span className={className}> <span className={className}>
<Countdown <Countdown
@ -20,9 +55,7 @@ export function LongCountdown ({ className, onComplete, date }) {
renderer={props => { renderer={props => {
return ( return (
<span suppressHydrationWarning> <span suppressHydrationWarning>
{props.formatted.days && `${props.formatted.days} days `} {formatter(props)}
{props.formatted.minutes && `${props.formatted.minutes} minutes `}
{props.formatted.seconds && `${props.formatted.seconds} seconds `}
</span> </span>
) )
}} }}

View File

@ -7,6 +7,7 @@ import { CURRENCY_SYMBOLS } from '@/lib/currency'
import { NORMAL_POLL_INTERVAL, SSR } from '@/lib/constants' import { NORMAL_POLL_INTERVAL, SSR } from '@/lib/constants'
import { useBlockHeight } from './block-height' import { useBlockHeight } from './block-height'
import { useChainFee } from './chain-fee' import { useChainFee } from './chain-fee'
import { CompactLongCountdown } from './countdown'
export const PriceContext = React.createContext({ export const PriceContext = React.createContext({
price: null, price: null,
@ -50,11 +51,9 @@ export default function Price ({ className }) {
}, []) }, [])
const { price, fiatSymbol } = usePrice() const { price, fiatSymbol } = usePrice()
const { height: blockHeight } = useBlockHeight() const { height: blockHeight, halving } = useBlockHeight()
const { fee: chainFee } = useChainFee() const { fee: chainFee } = useChainFee()
if (!price || price < 0 || blockHeight <= 0 || chainFee <= 0) return null
// Options: yep, 1btc, blockHeight, undefined // Options: yep, 1btc, blockHeight, undefined
// yep -> 1btc -> blockHeight -> chainFee -> undefined -> yep // yep -> 1btc -> blockHeight -> chainFee -> undefined -> yep
const handleClick = () => { const handleClick = () => {
@ -68,6 +67,9 @@ export default function Price ({ className }) {
window.localStorage.setItem('asSats', 'chainFee') window.localStorage.setItem('asSats', 'chainFee')
setAsSats('chainFee') setAsSats('chainFee')
} else if (asSats === 'chainFee') { } else if (asSats === 'chainFee') {
window.localStorage.setItem('asSats', 'halving')
setAsSats('halving')
} else if (asSats === 'halving') {
window.localStorage.removeItem('asSats') window.localStorage.removeItem('asSats')
setAsSats('fiat') setAsSats('fiat')
} else { } else {
@ -79,6 +81,7 @@ export default function Price ({ className }) {
const compClassName = (className || '') + ' text-reset pointer' const compClassName = (className || '') + ' text-reset pointer'
if (asSats === 'yep') { if (asSats === 'yep') {
if (!price || price < 0) return null
return ( return (
<div className={compClassName} onClick={handleClick} variant='link'> <div className={compClassName} onClick={handleClick} variant='link'>
{fixedDecimal(100000000 / price, 0) + ` sats/${fiatSymbol}`} {fixedDecimal(100000000 / price, 0) + ` sats/${fiatSymbol}`}
@ -95,6 +98,7 @@ export default function Price ({ className }) {
} }
if (asSats === 'blockHeight') { if (asSats === 'blockHeight') {
if (blockHeight <= 0) return null
return ( return (
<div className={compClassName} onClick={handleClick} variant='link'> <div className={compClassName} onClick={handleClick} variant='link'>
{blockHeight} {blockHeight}
@ -102,7 +106,17 @@ export default function Price ({ className }) {
) )
} }
if (asSats === 'halving') {
if (!halving) return null
return (
<div className={compClassName} onClick={handleClick} variant='link'>
<CompactLongCountdown date={halving} />
</div>
)
}
if (asSats === 'chainFee') { if (asSats === 'chainFee') {
if (chainFee <= 0) return null
return ( return (
<div className={compClassName} onClick={handleClick} variant='link'> <div className={compClassName} onClick={handleClick} variant='link'>
{chainFee} sat/vB {chainFee} sat/vB
@ -111,6 +125,7 @@ export default function Price ({ className }) {
} }
if (asSats === 'fiat') { if (asSats === 'fiat') {
if (!price || price < 0) return null
return ( return (
<div className={compClassName} onClick={handleClick} variant='link'> <div className={compClassName} onClick={handleClick} variant='link'>
{fiatSymbol + fixedDecimal(price, 0)} {fiatSymbol + fixedDecimal(price, 0)}

View File

@ -7,7 +7,6 @@ import Layout from '@/components/layout'
import { useMutation, useQuery } from '@apollo/client' import { useMutation, useQuery } from '@apollo/client'
import Link from 'next/link' import Link from 'next/link'
import { amountSchema } from '@/lib/validate' import { amountSchema } from '@/lib/validate'
import Countdown from 'react-countdown'
import { numWithUnits } from '@/lib/format' import { numWithUnits } from '@/lib/format'
import PageLoading from '@/components/page-loading' import PageLoading from '@/components/page-loading'
import { useShowModal } from '@/components/modal' import { useShowModal } from '@/components/modal'
@ -21,6 +20,7 @@ import { proportions } from '@/lib/madness'
import { useData } from '@/components/use-data' import { useData } from '@/components/use-data'
import { GrowthPieChartSkeleton } from '@/components/charts-skeletons' import { GrowthPieChartSkeleton } from '@/components/charts-skeletons'
import { useMemo } from 'react' import { useMemo } from 'react'
import { CompactLongCountdown } from '@/components/countdown'
const GrowthPieChart = dynamic(() => import('@/components/charts').then(mod => mod.GrowthPieChart), { const GrowthPieChart = dynamic(() => import('@/components/charts').then(mod => mod.GrowthPieChart), {
loading: () => <GrowthPieChartSkeleton /> loading: () => <GrowthPieChartSkeleton />
@ -77,15 +77,12 @@ export function RewardLine ({ total, time }) {
{numWithUnits(total)} in rewards {numWithUnits(total)} in rewards
</span> </span>
{time && {time &&
<Countdown <small style={{ whiteSpace: 'nowrap' }}>
date={time} <CompactLongCountdown
renderer={props => className='text-monospace'
<small className='text-monospace' suppressHydrationWarning style={{ whiteSpace: 'nowrap' }}> date={time}
{props.formatted.days />
? ` ${props.formatted.days}d ${props.formatted.hours}h ${props.formatted.minutes}m ${props.formatted.seconds}s` </small>}
: ` ${props.formatted.hours}:${props.formatted.minutes}:${props.formatted.seconds}`}
</small>}
/>}
</> </>
) )
} }