Skip to content

Commit

Permalink
feat(cr): claimable rewards
Browse files Browse the repository at this point in the history
  • Loading branch information
franciscotobar committed Nov 8, 2024
1 parent c5c4f11 commit 6d9659c
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 85 deletions.
119 changes: 119 additions & 0 deletions src/app/collective-rewards/builder/ClaimableRewards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { FC } from 'react'
import { Address } from 'viem'
import { useGetBuilderRewards } from '@/app/collective-rewards/hooks/useGetBuilderRewards'
import { usePricesContext } from '@/shared/context/PricesContext'
import { formatMetrics } from '@/app/collective-rewards/utils/formatMetrics'
import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils'
import { MetricsCardMultipleRowsWithSpinner } from '@/app/collective-rewards/components/MetricsCardMultipleRows'
import { Popover } from '@/components/Popover'
import {
useClaimBuilderRewards,
useClaimStateReporting,
} from '@/app/collective-rewards/builder/hooks/useClaimBuilderRewards'
import { useHandleErrors } from '@/app/collective-rewards/builder/LastCycleRewards'

type Token = {
symbol: string
address: Address
}

type ClaimableRewardsProps = {
builder: Address
gauge: Address
currency?: string
data: {
[token: string]: Token
}
}

const ClaimYourRewardsSvg = () => (
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.75 12.1667H13.7583M2.5 4.66667V16.3333C2.5 17.2538 3.24619 18 4.16667 18H15.8333C16.7538 18 17.5 17.2538 17.5 16.3333V8C17.5 7.07953 16.7538 6.33333 15.8333 6.33333L4.16667 6.33333C3.24619 6.33333 2.5 5.58714 2.5 4.66667ZM2.5 4.66667C2.5 3.74619 3.24619 3 4.16667 3H14.1667M14.1667 12.1667C14.1667 12.3968 13.9801 12.5833 13.75 12.5833C13.5199 12.5833 13.3333 12.3968 13.3333 12.1667C13.3333 11.9365 13.5199 11.75 13.75 11.75C13.9801 11.75 14.1667 11.9365 14.1667 12.1667Z"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)

const getAction = (onClick: () => void, disabled: boolean) => (
<Popover
content={
<div className="text-[12px] font-bold mb-1">
<p data-testid="builderAddressTooltip">Claim your rewards</p>
</div>
}
size="small"
position="top"
trigger="hover"
>
<button onClick={onClick} disabled={disabled}>
<ClaimYourRewardsSvg />
</button>
</Popover>
)

export const ClaimableRewards: FC<ClaimableRewardsProps> = ({
builder,
gauge,
data: { rif, rbtc },
currency = 'USD',
}) => {
const { prices } = usePricesContext()

const useGetRewardMetrics = (tokenAddress: Address, symbol: string, currency: string) => {
const { data: rewards, isLoading, error } = useGetBuilderRewards(tokenAddress, gauge)

const price = prices[symbol]?.price ?? 0
const rewardsInHuman = Number(formatBalanceToHuman(rewards ?? 0n))
const rewardMetrics = formatMetrics(rewardsInHuman, price, symbol, currency)

const { isClaimFunctionReady, claimRewards, ...claimTx } = useClaimBuilderRewards(builder, tokenAddress)
useClaimStateReporting({ ...claimTx })

const action = getAction(claimRewards, !isClaimFunctionReady)

return {
data: {
...rewardMetrics,
action,
},
error,
isLoading,
}
}

const {
data: rifRewardsMetrics,
isLoading: rifLoading,
error: rifError,
} = useGetRewardMetrics(rif.address, rif.symbol, currency)
const {
data: rbtcRewardsMetrics,
isLoading: rbtcLoading,
error: rbtcError,
} = useGetRewardMetrics(rbtc.address, rbtc.symbol, currency)

useHandleErrors([
{ error: rifError, title: 'Error loading builder rif rewards' },
{ error: rbtcError, title: 'Error loading builder rbtc rewards' },
])

const isLoading = rifLoading || rbtcLoading

return (
<div>
<MetricsCardMultipleRowsWithSpinner
title="Claimable rewards"
data={{
rif: rifRewardsMetrics,
rbtc: rbtcRewardsMetrics,
}}
isLoading={isLoading}
borderless
/>
</div>
)
}
14 changes: 8 additions & 6 deletions src/app/collective-rewards/builder/LastCycleRewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ import {
import { Cycle, useCycleContext } from '@/app/collective-rewards/CycleContext'
import { MetricsCardMultipleRowsWithSpinner } from '@/app/collective-rewards/components/MetricsCardMultipleRows'
import { getLastCycleRewards } from '@/app/collective-rewards/builder/utils/getLastCycleRewards'
import { formatMetrics } from '@/app/collective-rewards/builder/utils/formatMetrics'
import { formatMetrics } from '@/app/collective-rewards/utils/formatMetrics'

type Token = {
symbol: string
address: Address
}

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

const useHandleErrors = (errors: { error: Error | null; title: string }[]) => {
export const useHandleErrors = (errors: { error: Error | null; title: string }[]) => {
const { setMessage } = useAlertContext()

useEffect(() => {
Expand Down
77 changes: 42 additions & 35 deletions src/app/collective-rewards/builder/Rewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ import { tokenContracts } from '@/lib/contracts'
import { PricesContextProvider } from '@/shared/context/PricesContext'
import { FC } from 'react'
import { Button } from '@/components/Button'
import { useClaimAllRewards, useClaimStateReporting } from '@/app/collective-rewards/hooks/useClaimAllRewards'
import {
useClaimBuilderRewards,
useClaimStateReporting,
} from '@/app/collective-rewards/builder/hooks/useClaimBuilderRewards'
import { Popover } from '@/components/Popover'
import { getCoinBaseAddress } from '@/app/collective-rewards/utils/getCoinBaseAddress'
import { CycleContextProvider } from '@/app/collective-rewards/CycleContext'
import { LastCycleRewards } from '@/app/collective-rewards/builder/LastCycleRewards'
import { ClaimableRewards } from '@/app/collective-rewards/builder/ClaimableRewards'

type RewardsProps = {
builder: Address
gauge: Address
}

export const Rewards: FC<RewardsProps> = ({ builder, gauge }) => {
const { isClaimFunctionReady, claimAllRewards, ...claimTx } = useClaimAllRewards(builder)
const { isClaimFunctionReady, claimRewards, ...claimTx } = useClaimBuilderRewards(builder)

useClaimStateReporting({ ...claimTx })

Expand All @@ -33,41 +37,44 @@ export const Rewards: FC<RewardsProps> = ({ builder, gauge }) => {

return (
<>
<>
<div className="mb-6">
<HeaderTitle className="font-normal">As a Builder</HeaderTitle>
<Typography tagVariant="p" className="text-[14px]">
{' '}
Monitor the rewards you are getting from your Collective Rewards.
</Typography>
</div>
<CycleContextProvider>
<PricesContextProvider>
<div className="grid grid-cols-5 gap-[16px] ">
<div className="mb-6">
<HeaderTitle className="font-normal">As a Builder</HeaderTitle>
<Typography tagVariant="p" className="text-[14px]">
{' '}
Monitor the rewards you are getting from your Collective Rewards.
</Typography>
</div>
<div className="flex flex-col gap-y-2">
<div className="grid grid-cols-5 gap-[16px]">
<CycleContextProvider>
<PricesContextProvider>
<ClaimableRewards builder={builder} gauge={gauge} data={data} />
<LastCycleRewards gauge={gauge} data={data} />
</div>
</PricesContextProvider>
</CycleContextProvider>
</>
</PricesContextProvider>
</CycleContextProvider>
</div>

<Popover
disabled={isClaimFunctionReady}
content={
<Paragraph variant="normal" className="text-sm">
Wait a moment, please. Preparing the claim functionality.
</Paragraph>
}
trigger="hover"
background="dark"
size="small"
position="bottom"
className="z-[100]"
>
<Button onClick={claimAllRewards} disabled={!isClaimFunctionReady} variant="primary">
{/* TODO: adapt size once metrics are in place */}
Claim all
</Button>
</Popover>
<div className="grid grid-cols-5 gap-[16px]">
<Popover
disabled={isClaimFunctionReady}
content={
<Paragraph variant="normal" className="text-sm">
Wait a moment, please. Preparing the claim functionality.
</Paragraph>
}
trigger="hover"
background="dark"
size="small"
position="bottom"
className="z-[100]"
>
<Button onClick={claimRewards} disabled={!isClaimFunctionReady} variant="primary">
{/* TODO: adapt size once metrics are in place */}
Claim all
</Button>
</Popover>
</div>
</div>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useEffect, useMemo } from 'react'
import { useAlertContext } from '@/app/providers'
import { useGetBuilderToGauge } from '@/app/collective-rewards/hooks/useGetBuilderToGauge'

export const useClaimAllRewards = (builder: Address) => {
export const useClaimBuilderRewards = (builder: Address, rewardToken?: Address) => {
const { writeContractAsync, error: executionError, data: hash, isPending } = useWriteContract()
const {
data: gauge,
Expand Down Expand Up @@ -52,18 +52,18 @@ export const useClaimAllRewards = (builder: Address) => {
}
}, [gauge, gaugeError, builder, executionError, receiptError, isFetched])

const claimAllRewards = () => {
const claimBuilderReward = () => {
return writeContractAsync({
abi: GaugeAbi,
address: gauge as Address,
functionName: 'claimBuilderReward',
args: [],
args: rewardToken ? [rewardToken] : [],
})
}

return {
isClaimFunctionReady,
claimAllRewards: () => isClaimFunctionReady && claimAllRewards(),
claimRewards: () => isClaimFunctionReady && claimBuilderReward(),
error,
isPendingTx: isPending,
isLoadingReceipt: isLoading,
Expand All @@ -78,7 +78,7 @@ export const useClaimStateReporting = ({
isLoadingReceipt,
isSuccess,
receipt,
}: Omit<ReturnType<typeof useClaimAllRewards>, 'isClaimFunctionReady' | 'claimAllRewards'>) => {
}: Omit<ReturnType<typeof useClaimBuilderRewards>, 'isClaimFunctionReady' | 'claimRewards'>) => {
const { setMessage } = useAlertContext()

useEffect(() => {
Expand Down
65 changes: 26 additions & 39 deletions src/app/collective-rewards/components/MetricsCardMultipleRows.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,48 @@ import { Span, Typography } from '../../../components/Typography'
type MetricsCardRow = {
amount: string
fiatAmount?: string
action?: JSX.Element
}

type MetricsCardMultipleRowsProps = {
/**
* 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.
*/
borderless?: boolean

/**
* The address of the contract to link to.
*/
contractAddress?: Address
'data-testid'?: string
}

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 MetricsCardRow: FC<MetricsCardRow> = ({ amount, fiatAmount, action }) => (
<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>
{action}
</div>
)

/**
* Card for displaying balance and corresponding (fiat) value.
*/
const DEFAULT_CLASSES = 'h-min-[79px] w-full py-[12px] px-[12px] flex flex-col bg-foreground'
export const MetricsCardMultipleRows: FC<MetricsCardMultipleRowsProps> = ({
title,
borderless = false,
Expand All @@ -73,7 +60,7 @@ export const MetricsCardMultipleRows: FC<MetricsCardMultipleRowsProps> = ({
}) => {
const borderClasses = borderless ? '' : 'border border-white border-opacity-40 rounded-lg'
return (
<div className={cn(DEFAULT_CLASSES, borderClasses)} data-testid={dataTestId || 'MetricsCard'}>
<div className={cn(DEFAULT_CLASSES, borderClasses)} data-testid={dataTestId || 'MetricsCardMultipleRows'}>
{typeof title === 'string' ? (
<div>
<Typography
Expand Down
Loading

0 comments on commit 6d9659c

Please sign in to comment.