Skip to content

Commit

Permalink
refactor: make use of composition for cards
Browse files Browse the repository at this point in the history
  • Loading branch information
franciscotobar committed Nov 11, 2024
1 parent 9d6d74b commit 2e3ccc3
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 96 deletions.
85 changes: 49 additions & 36 deletions src/app/collective-rewards/rewards/LastCycleRewards.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,74 @@
import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils'
import { Address } from 'viem'
import { usePricesContext } from '@/shared/context/PricesContext'
import { FC } from 'react'
import { Cycle, useCycleContext } from '@/app/collective-rewards/metrics/context/CycleContext'
import { useCycleContext } from '@/app/collective-rewards/metrics/context/CycleContext'
import {
NotifyRewardEventLog,
useGetNotifyRewardLogs,
MetricsCardWithSpinner,
formatMetrics,
getLastCycleRewards,
MetricsCardTitle,
MetricsCardWithSpinner,
TokenMetricsCardRow,
useGetNotifyRewardLogs,
} from '@/app/collective-rewards/rewards'
import { useHandleErrors } from '@/app/collective-rewards/utils'
import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils'
import { usePricesContext } from '@/shared/context/PricesContext'
import { FC, useEffect, useState } from 'react'
import { Address } from 'viem'

type LastCycleRewardsProps = {
type TokenRewardsProps = {
gauge: Address
currency?: string
data: {
[token: string]: {
symbol: string
address: Address
}
token: {
symbol: string
address: Address
}
setState: (state: { isLoading: boolean }) => void
}

const useGetRewardMetrics = (cycle: Cycle, logs: NotifyRewardEventLog, symbol: string, currency: string) => {
const { prices } = usePricesContext()

const lastCycleRewards = getLastCycleRewards(cycle, logs)
const lastCycleRewardsInHuman = Number(formatBalanceToHuman(lastCycleRewards.builderAmount))
const price = prices[symbol]?.price ?? 0

return formatMetrics(lastCycleRewardsInHuman, price, symbol, currency)
}

export const LastCycleRewards: FC<LastCycleRewardsProps> = ({
const RewardsTokenMetrics: FC<TokenRewardsProps> = ({
gauge,
data: { rif, rbtc },
token: { symbol, address },
currency = 'USD',
setState,
}) => {
const { data: cycle, isLoading: cycleLoading, error: cycleError } = useCycleContext()
const { data: rewardsPerToken, isLoading: logsLoading, error: rewardsError } = useGetNotifyRewardLogs(gauge)

const error = cycleError ?? rewardsError

useHandleErrors([{ error: error, title: 'Error loading last cycle rewards' }])

const rifRewardsMetrics = useGetRewardMetrics(cycle, rewardsPerToken[rif.address], rif.symbol, currency)
const rbtcRewardsMetrics = useGetRewardMetrics(cycle, rewardsPerToken[rbtc.address], rbtc.symbol, currency)
const { prices } = usePricesContext()

const lastCycleRewards = getLastCycleRewards(cycle, rewardsPerToken[address])
const lastCycleRewardsInHuman = Number(formatBalanceToHuman(lastCycleRewards.builderAmount))
const price = prices[symbol]?.price ?? 0
const { amount, fiatAmount } = formatMetrics(lastCycleRewardsInHuman, price, symbol, currency)

useEffect(() => {
setState({ isLoading: cycleLoading || logsLoading })
}, [cycleLoading, logsLoading, setState])

return <TokenMetricsCardRow amount={amount} fiatAmount={fiatAmount} />
}

type LastCycleRewardsProps = {
gauge: Address
data: {
[token: string]: {
symbol: string
address: Address
}
}
currency?: string
}

const isLoading = cycleLoading || logsLoading
export const LastCycleRewards: FC<LastCycleRewardsProps> = ({ data: { rif, rbtc }, ...rest }) => {
const [{ isLoading: isLoadingRif }, setRifState] = useState({ isLoading: false })
const [{ isLoading: isLoadingRbtc }, setRbtcState] = useState({ isLoading: false })

return (
<MetricsCardWithSpinner
title="Last cycle rewards"
data={{ rif: rifRewardsMetrics, rbtc: rbtcRewardsMetrics }}
isLoading={isLoading}
borderless
/>
<MetricsCardWithSpinner isLoading={isLoadingRif || isLoadingRbtc} borderless>
<MetricsCardTitle title="Last cycle rewards" data-testid="LastCycleRewards" />
<RewardsTokenMetrics {...rest} token={rif} setState={() => setRifState} />
<RewardsTokenMetrics {...rest} token={rbtc} setState={() => setRbtcState} />
</MetricsCardWithSpinner>
)
}
105 changes: 47 additions & 58 deletions src/app/collective-rewards/rewards/components/Metrics/MetricsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
import { cn, shortAddress } from '@/lib/utils'
import { cn } from '@/lib/utils'
import { FC, ReactNode } from 'react'
import { Address } from 'viem'
import { BoxIcon } from 'lucide-react'
import { EXPLORER_URL } from '@/lib/constants'
import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner'
import { Span, Typography } from '@/components/Typography'
import { Typography } from '@/components/Typography'

type MetricsCardRow = {
amount: string
fiatAmount?: string
action?: JSX.Element
children?: ReactNode
}

type MetricsCardProps = {
/**
* The title of the card, usually indicating the type of balance.
*/
title: ReactNode

data: {
[token: string]: MetricsCardRow
}

/**
* Whether the card should have a border or not.
*/
Expand All @@ -31,73 +22,71 @@ type MetricsCardProps = {
*/
contractAddress?: Address
'data-testid'?: string

/**
* The children of the card. Usually a MetricsCardRow.
*/
children?: ReactNode
}

const DEFAULT_CLASSES = 'h-min-[79px] w-full py-[12px] px-[12px] flex flex-col bg-foreground'

export const MetricsCardRow: FC<MetricsCardRow> = ({ amount, fiatAmount }) => (
<div>
<Typography
tagVariant="h2"
paddingBottom="2px"
paddingTop="10px"
lineHeight="28.8px"
fontFamily="kk-topo"
className="text-[24px] text-primary font-normal"
data-testid="Amount"
>
{amount}
</Typography>
{fiatAmount && (
export const TokenMetricsCardRow: FC<MetricsCardRow> = ({ amount, fiatAmount, children }) => (
<div className="flex flex-row w-full items-center">
<div className="flex-1 min-w-0">
<Typography
tagVariant="label"
className="text-[14px] font-rootstock-sans text-disabled-primary"
lineHeight="14px"
data-testid="FiatAmount"
tagVariant="h2"
paddingBottom="2px"
paddingTop="10px"
lineHeight="28.8px"
fontFamily="kk-topo"
className="text-[24px] text-primary font-normal"
data-testid="Amount"
>
{fiatAmount}
{amount}
</Typography>
)}
{fiatAmount && (
<Typography
tagVariant="label"
className="text-[14px] font-rootstock-sans text-disabled-primary"
lineHeight="14px"
data-testid="FiatAmount"
>
{fiatAmount}
</Typography>
)}
</div>
{children}
</div>
)

/**
* Card for displaying balance and corresponding (fiat) value.
*/
export const MetricsCard: FC<MetricsCardProps> = ({
title,
borderless = false,
data: { rif, rbtc },
contractAddress,
children,
'data-testid': dataTestId,
}) => {
const borderClasses = borderless ? '' : 'border border-white border-opacity-40 rounded-lg'
return (
<div className={cn(DEFAULT_CLASSES, borderClasses)} data-testid={dataTestId || 'MetricsCard'}>
{typeof title === 'string' ? (
<div>
<Typography
tagVariant="label"
className="text-[16px] font-normal tracking-wide overflow-hidden whitespace-nowrap text-ellipsis"
>
{title}
</Typography>
</div>
) : (
title
)}
<MetricsCardRow {...rif} />
<MetricsCardRow {...rbtc} />
{contractAddress && (
<a href={`${EXPLORER_URL}/address/${contractAddress}`} target="_blank" className="mt-2">
<BoxIcon size={20} className="inline-block mr-1" />
<Span className="underline" size="small">
{shortAddress(contractAddress)}
</Span>
</a>
)}
{children}
</div>
)
}

export const MetricsCardTitle: FC<{ title: string; 'data-testid': string }> = ({
title,
'data-testid': dataTestId,
}) => (
<Typography
tagVariant="label"
className="text-[16px] font-normal tracking-wide overflow-hidden whitespace-nowrap text-ellipsis"
data-testid={`${dataTestId}_MetricsCardTitle`}
>
{title}
</Typography>
)

export const MetricsCardWithSpinner = withSpinner(MetricsCard)
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ export type NotifyRewardEventLog = ReturnType<typeof parseEventLogs<typeof Gauge

export type NotifyRewardsPerToken = Record<Address, NotifyRewardEventLog>

export const useGetNotifyRewardLogs = (gauge?: Address) => {
export const useGetNotifyRewardLogs = (gauge: Address) => {
const { data, error, isLoading } = useQuery({
queryFn: async () => {
const { data } = await fetchNotifyRewardLogs(gauge!)
const { data } = await fetchNotifyRewardLogs(gauge)

const events = parseEventLogs({
abi: GaugeAbi,
Expand Down

0 comments on commit 2e3ccc3

Please sign in to comment.