Skip to content

Commit

Permalink
feat: claim nft (#4235)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomquirk authored Jan 27, 2024
1 parent 3440e7b commit 5261ad3
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const NftRewardsPanel = () => {
<Trans>NFTs</Trans>
</h2>
<RedeemNftsSection />

{!nftsLoading && rewardTiers?.length ? (
<div className="grid grid-cols-2 gap-4 md:grid-cols-3 md:gap-6">
{rewardTiers?.map((tier, i) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Trans } from '@lingui/macro'
import TooltipIcon from 'components/TooltipIcon'
import ETHAmount from 'components/currency/ETHAmount'
import { BigNumber } from 'ethers'

export function NftCreditsSection({ credits }: { credits: BigNumber }) {
return (
<>
<div className="text-sm font-medium text-grey-600 dark:text-slate-50">
<Trans>Your credits</Trans>
</div>
<div className="font-heading text-xl font-medium dark:text-slate-50">
<ETHAmount amount={credits} /> credits{' '}
<TooltipIcon
tip={
<Trans>
You have NFT credits from previous payments. Select NFTs to mint
and use your credits.
</Trans>
}
/>
</div>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,47 +3,76 @@ import { Button } from 'antd'
import { RedeemNftsModal } from 'components/v2v3/V2V3Project/ManageNftsSection/RedeemNftsModal/RedeemNftsModal'
import { V2V3ProjectContext } from 'contexts/v2v3/Project/V2V3ProjectContext'
import { useNftAccountBalance } from 'hooks/JB721Delegate/useNftAccountBalance'
import { useNftCredits } from 'hooks/JB721Delegate/useNftCredits'
import { useWallet } from 'hooks/Wallet'
import { useContext, useState } from 'react'
import { NftCreditsSection } from './NftCreditsSection'
import { RedeemNftTiles } from './RedeemNftTiles'

export function RedeemNftsSection() {
const [redeemNftsModalVisible, setRedeemNftsModalVisible] = useState(false)

const { userAddress } = useWallet()
const { fundingCycleMetadata, primaryTerminalCurrentOverflow } =
useContext(V2V3ProjectContext)
const { data, loading } = useNftAccountBalance({
accountAddress: userAddress,
dataSourceAddress: fundingCycleMetadata?.dataSource,
})
const { data: credits, loading: loadingCredits } = useNftCredits(userAddress)

const hasOverflow = primaryTerminalCurrentOverflow?.gt(0)
const hasRedemptionRate = fundingCycleMetadata?.redemptionRate.gt(0)
const canRedeem = hasOverflow && hasRedemptionRate
const canRedeem =
hasOverflow &&
hasRedemptionRate &&
fundingCycleMetadata?.useDataSourceForRedeem

const hasRedeemableNfts = (data?.nfts?.length ?? 0) > 0

if (loading || !hasRedeemableNfts || !userAddress) return null
const showRedeemSection = !loading && hasRedeemableNfts && userAddress
const showCreditSection = !loadingCredits && credits && credits.gt(0)

if (!showRedeemSection && !showCreditSection) return null

return (
<div className="h-32 w-full rounded-lg bg-smoke-50 p-5 dark:bg-slate-700">
<div className="text-sm font-medium text-grey-600 dark:text-slate-50">
<Trans>Your NFTs</Trans>
</div>

<div className="mt-4 flex items-end justify-between">
<RedeemNftTiles nftAccountBalance={data} />

<Button type="primary" onClick={() => setRedeemNftsModalVisible(true)}>
{canRedeem ? <Trans>Redeem NFTs</Trans> : <Trans>Burn NFTs</Trans>}
</Button>
</div>

{redeemNftsModalVisible && (
<RedeemNftsModal
open={redeemNftsModalVisible}
onCancel={() => setRedeemNftsModalVisible(false)}
onConfirmed={() => setRedeemNftsModalVisible(false)}
/>
)}
<div className="flex w-full flex-col gap-4 rounded-lg bg-smoke-50 p-5 dark:bg-slate-700">
{showCreditSection ? (
<div>
<NftCreditsSection credits={credits} />
</div>
) : null}

{showRedeemSection ? (
<div>
<div className="text-sm font-medium text-grey-600 dark:text-slate-50">
<Trans>Your NFTs</Trans>
</div>

<div className="mt-3 flex items-end justify-between">
<RedeemNftTiles nftAccountBalance={data} />

<Button
type="primary"
onClick={() => setRedeemNftsModalVisible(true)}
>
{canRedeem ? (
<Trans>Redeem NFTs</Trans>
) : (
<Trans>Burn NFTs</Trans>
)}
</Button>
</div>

{redeemNftsModalVisible && (
<RedeemNftsModal
open={redeemNftsModalVisible}
onCancel={() => setRedeemNftsModalVisible(false)}
onConfirmed={() => setRedeemNftsModalVisible(false)}
/>
)}
</div>
) : null}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Trans } from '@lingui/macro'
import { useTokensPerEth } from 'components/v2v3/V2V3Project/ProjectDashboard/hooks/useTokensPerEth'
import { V2V3CurrencyOption } from 'models/v2v3/currencyOption'
import { V2V3_CURRENCY_ETH } from 'utils/v2v3/currency'
import { useTokensPerEth } from '../../../hooks/useTokensPerEth'

export const TokensPerEth = ({
currencyAmount,
Expand All @@ -13,7 +14,10 @@ export const TokensPerEth = ({
| undefined
}) => {
const { currencyText, receivedTickets, receivedTokenSymbolText } =
useTokensPerEth(currencyAmount)
useTokensPerEth({
amount: parseFloat(currencyAmount?.amount.toString() || '1'),
currency: currencyAmount?.currency || V2V3_CURRENCY_ETH,
})

const suffix =
!currencyAmount || !currencyAmount.amount ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import { ProjectCartContext, ProjectCartProvider } from './ProjectCartProvider'

jest.mock('contexts/NftRewards/NftRewardsContext')

jest.mock('hooks/Wallet', () => ({
useWallet: () => ({
userAddress: 'userAddress',
}),
}))

describe('ProjectCartProvider', () => {
const DefaultNftRewardsContext = {
nftRewards: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { NftRewardsContext } from 'contexts/NftRewards/NftRewardsContext'
import { BigNumber } from 'ethers'
import { formatEther } from 'ethers/lib/utils'
import { useNftCredits } from 'hooks/JB721Delegate/useNftCredits'
import { useWallet } from 'hooks/Wallet'
import { useCurrencyConverter } from 'hooks/useCurrencyConverter'
import { V2V3CurrencyOption } from 'models/v2v3/currencyOption'
import React, { createContext, useContext, useMemo, useReducer } from 'react'
Expand Down Expand Up @@ -45,6 +49,12 @@ export const ProjectCartProvider = ({
}: {
children: React.ReactNode
}) => {
const { userAddress } = useWallet()
const userNftCredits = useNftCredits(userAddress)
const userNftCreditsNumber = parseFloat(
formatEther(userNftCredits.data ?? BigNumber.from(0)),
)

const [state, dispatch] = useReducer(projectCartReducer, {
payAmount: undefined,
nftRewards: [],
Expand Down Expand Up @@ -72,16 +82,22 @@ export const ProjectCartProvider = ({
nft.quantity,
0,
)
if (nftRewardsTotal > 0) {
nftRewardsTotal -= userNftCreditsNumber
}
if (state.payAmount?.currency === V2V3_CURRENCY_USD) {
nftRewardsTotal =
converter.weiToUsd(parseWad(nftRewardsTotal))?.toNumber() ?? 0
}

const payAmount = state.payAmount?.amount ?? 0

return {
amount: payAmount + nftRewardsTotal,
currency: state.payAmount?.currency ?? V2V3_CURRENCY_ETH,
}
}, [
userNftCreditsNumber,
converter,
rewardTiers,
state.nftRewards,
Expand All @@ -94,6 +110,7 @@ export const ProjectCartProvider = ({
...state,
visible,
totalAmount,
userNftCredits,
}

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useProjectContext } from 'components/v2v3/V2V3Project/ProjectDashboard/hooks/useProjectContext'
import { CurrencyContext } from 'contexts/shared/CurrencyContext'
import { BigNumber, utils } from 'ethers'
import { useCurrencyConverter } from 'hooks/useCurrencyConverter'
import { BigNumber } from 'ethers'
import useWeiConverter from 'hooks/useWeiConverter'
import { CurrencyOption } from 'models/currencyOption'
import { V2V3CurrencyOption } from 'models/v2v3/currencyOption'
Expand Down Expand Up @@ -31,8 +30,6 @@ export function useTokensPerEth(
const reservedRate = reservedRateBigNumber?.toNumber()
const { weight } = fundingCycle ?? {}

const converter = useCurrencyConverter()

const weiPayAmt = useWeiConverter<CurrencyOption>({
currency,
amount: amount?.toString(),
Expand All @@ -53,16 +50,7 @@ export function useTokensPerEth(
[weight, reservedRate],
)

const receivedTickets = useMemo(() => {
if (weiPayAmt.gt(0)) {
return formatReceivedTickets(weiPayAmt)
}
return formatReceivedTickets(
(currency === V2V3_CURRENCY_ETH
? utils.parseEther('1')
: converter.usdToWei('1')) ?? BigNumber.from(0),
)
}, [converter, currency, formatReceivedTickets, weiPayAmt])
const receivedTickets = formatReceivedTickets(weiPayAmt)

const receivedTokenSymbolText = useMemo(
() =>
Expand Down
22 changes: 22 additions & 0 deletions src/hooks/JB721Delegate/useNftCredits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { JB721DelegateContractsContext } from 'contexts/NftRewards/JB721DelegateContracts/JB721DelegateContractsContext'
import { BigNumber } from 'ethers'
import useV2ContractReader from 'hooks/v2v3/contractReader/useV2ContractReader'
import { useContext } from 'react'

/**
* Return the wei amount of unused credits for a given address.
* @example If the user paid 1 ETH and didnt mint a 1 ETH NFT, then they have 1 ETH of unused credits.
* @param address
* @returns
*/
export function useNftCredits(address: string | undefined) {
const {
contracts: { JB721TieredDelegate },
} = useContext(JB721DelegateContractsContext)

return useV2ContractReader<BigNumber>({
contract: JB721TieredDelegate,
functionName: 'creditsOf',
args: address ? [address] : null,
})
}
6 changes: 6 additions & 0 deletions src/locales/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,9 @@ msgstr ""
msgid "Amount to pay out"
msgstr ""

msgid "You have NFT credits from previous payments. Select NFTs to mint and use your credits."
msgstr ""

msgid "JuiceboxDAO"
msgstr ""

Expand Down Expand Up @@ -2117,6 +2120,9 @@ msgstr ""
msgid "Enable NFTs"
msgstr ""

msgid "Your credits"
msgstr ""

msgid "Save token recipients"
msgstr ""

Expand Down

2 comments on commit 5261ad3

@vercel
Copy link

@vercel vercel bot commented on 5261ad3 Jan 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 5261ad3 Jan 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.