Skip to content

Commit

Permalink
Apply v4 NFT credit on checkout (#4542)
Browse files Browse the repository at this point in the history
  • Loading branch information
wraeth-eth authored Nov 29, 2024
1 parent 0322064 commit 0bfe8e0
Show file tree
Hide file tree
Showing 10 changed files with 309 additions and 89 deletions.
3 changes: 0 additions & 3 deletions src/locales/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1229,9 +1229,6 @@ msgstr ""
msgid "Issue as ERC-20"
msgstr ""

msgid "Pay {primaryAmount}"
msgstr ""

msgid "Active"
msgstr ""

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useWallet } from 'hooks/Wallet'
import { useReadJb721TiersHookPayCreditsOf } from 'juice-sdk-react'
import { useV4NftRewards } from 'packages/v4/contexts/V4NftRewards/V4NftRewardsProvider'
import { useV4UserNftCredits } from 'packages/v4/contexts/V4UserNftCreditsProvider'
import { V4CurrencyOption } from 'packages/v4/models/v4CurrencyOption'
import React from 'react'
import { useProjectDispatch } from './redux/hooks'
Expand All @@ -25,12 +24,7 @@ export const ReduxProjectCartProvider = ({
const {
nftRewards: { rewardTiers },
} = useV4NftRewards()

const { userAddress } = useWallet()
const { data: nftCredits } = useReadJb721TiersHookPayCreditsOf({
address: userAddress,
})

const nftCredits = useV4UserNftCredits()
const dispatch = useProjectDispatch()

// Set the nfts on load
Expand All @@ -40,8 +34,9 @@ export const ReduxProjectCartProvider = ({

// Set the user's NFT credits on load
React.useEffect(() => {
dispatch(projectCartActions.setUserNftCredits(nftCredits ?? 0n))
}, [dispatch, nftCredits])
if (nftCredits.isLoading) return
dispatch(projectCartActions.setUserNftCredits(nftCredits.data ?? 0n))
}, [dispatch, nftCredits.isLoading, nftCredits.data])

return <>{children}</>
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import EtherscanLink from 'components/EtherscanLink'
import ExternalLink from 'components/ExternalLink'
import { JuiceModal } from 'components/modals/JuiceModal'
import { Formik } from 'formik'
import Image from "next/legacy/image"
import Image from 'next/legacy/image'
import { useV4UserNftCredits } from 'packages/v4/contexts/V4UserNftCreditsProvider'
import { twMerge } from 'tailwind-merge'
import { helpPagePath } from 'utils/helpPagePath'
import { MessageSection } from './components/MessageSection'
import { ReceiveSection } from './components/ReceiveSection'
import { usePayAmounts } from './hooks/usePayAmounts'
import {
PayProjectModalFormValues,
usePayProjectModal,
Expand All @@ -16,8 +18,6 @@ import {
export const PayProjectModal: React.FC = () => {
const {
open,
primaryAmount,
secondaryAmount,
validationSchema,
isTransactionPending,
isTransactionConfirmed,
Expand All @@ -27,6 +27,7 @@ export const PayProjectModal: React.FC = () => {
setOpen,
onPaySubmit,
} = usePayProjectModal()
const { formattedTotalAmount } = usePayAmounts()

return (
<Formik<PayProjectModalFormValues>
Expand All @@ -50,7 +51,7 @@ export const PayProjectModal: React.FC = () => {
position="top"
okLoading={props.isSubmitting || isTransactionPending}
okButtonForm="PayProjectModalForm"
okText={t`Pay ${primaryAmount}`}
okText={t`Pay ${formattedTotalAmount.primaryAmount}`}
cancelText={
isTransactionPending || isTransactionConfirmed
? t`Close`
Expand Down Expand Up @@ -97,19 +98,7 @@ export const PayProjectModal: React.FC = () => {
) : (
<>
<div className="flex flex-col divide-y divide-grey-200 dark:divide-slate-500">
<div className="flex justify-between gap-3 py-3">
<span className="font-medium">
<Trans>Total amount</Trans>
</span>
<div>
<span>{primaryAmount}</span>{' '}
{secondaryAmount && (
<span className="text-grey-500 dark:text-slate-200">
({secondaryAmount})
</span>
)}
</div>
</div>
<AmountSection />

<ReceiveSection className="py-6" />

Expand Down Expand Up @@ -172,3 +161,60 @@ export const PayProjectModal: React.FC = () => {
</Formik>
)
}

const AmountSection = () => {
const { data: nftCredits } = useV4UserNftCredits()
const { formattedAmount, formattedNftCredits, formattedTotalAmount } =
usePayAmounts()

const RowData = ({
label,
primaryAmount,
secondaryAmount,
}: {
label: React.ReactNode
primaryAmount: React.ReactNode
secondaryAmount: React.ReactNode
}) => (
<div className="flex justify-between gap-3 py-3">
<span className="font-medium">{label}</span>
<div>
<span>{primaryAmount}</span>{' '}
{secondaryAmount && (
<span className="text-grey-500 dark:text-slate-200">
({secondaryAmount})
</span>
)}
</div>
</div>
)

if (!nftCredits || nftCredits <= 0n || !formattedNftCredits)
return (
<RowData
label={t`Total amount`}
primaryAmount={formattedTotalAmount?.primaryAmount}
secondaryAmount={formattedTotalAmount?.secondaryAmount}
/>
)

return (
<div>
<RowData
label={t`Amount`}
primaryAmount={formattedAmount.primaryAmount}
secondaryAmount={formattedAmount.secondaryAmount}
/>
<RowData
label={t`NFT Credits`}
primaryAmount={`-${formattedNftCredits.primaryAmount}`}
secondaryAmount={`-${formattedNftCredits.secondaryAmount}`}
/>
<RowData
label={t`Total amount`}
primaryAmount={formattedTotalAmount?.primaryAmount}
secondaryAmount={formattedTotalAmount?.secondaryAmount}
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { useCurrencyConverter } from 'hooks/useCurrencyConverter'
import { useV4UserNftCredits } from 'packages/v4/contexts/V4UserNftCreditsProvider'
import { V4_CURRENCY_ETH, V4_CURRENCY_USD } from 'packages/v4/utils/currency'
import { formatCurrencyAmount } from 'packages/v4/utils/formatCurrencyAmount'
import React from 'react'
import { fromWad, parseWad } from 'utils/format/formatNumber'
import { useProjectSelector } from '../../../redux/hooks'
import { usePayProjectModal } from './usePayProjectModal/usePayProjectModal'

export const usePayAmounts = () => {
const converter = useCurrencyConverter()
const { payAmount } = useProjectSelector(state => state.projectCart)
const { primaryAmount, secondaryAmount } = usePayProjectModal()
const { data: nftCreditsData } = useV4UserNftCredits()

const payAmountRaw = React.useMemo(() => {
if (!payAmount) return

switch (payAmount.currency) {
case V4_CURRENCY_ETH:
return {
eth: parseWad(payAmount.amount),
usd: converter.weiToUsd(parseWad(payAmount.amount))!,
}
case V4_CURRENCY_USD:
return {
eth: converter.usdToWei(payAmount.amount),
usd: parseWad(payAmount.amount),
}
}
}, [converter, payAmount])

const appliedNFTCreditsRaw = React.useMemo(() => {
if (!payAmountRaw || !nftCreditsData) return

const nftCreditsApplied = payAmountRaw.eth.lt(nftCreditsData)
? payAmountRaw.eth
: nftCreditsData

const eth = nftCreditsApplied
const usd = parseWad(converter.weiToUsd(nftCreditsApplied))!

return {
eth,
usd,
}
}, [converter, nftCreditsData, payAmountRaw])

const formattedNftCredits = React.useMemo(() => {
if (!appliedNFTCreditsRaw || !payAmount) return

switch (payAmount.currency) {
case V4_CURRENCY_ETH:
return {
primaryAmount: formatCurrencyAmount({
amount: fromWad(appliedNFTCreditsRaw.eth),
currency: V4_CURRENCY_ETH,
}),
secondaryAmount: formatCurrencyAmount({
amount: fromWad(appliedNFTCreditsRaw.usd),
currency: V4_CURRENCY_USD,
}),
}
case V4_CURRENCY_USD:
return {
primaryAmount: formatCurrencyAmount({
amount: fromWad(appliedNFTCreditsRaw.usd),
currency: V4_CURRENCY_USD,
}),
secondaryAmount: formatCurrencyAmount({
amount: fromWad(appliedNFTCreditsRaw.eth),
currency: V4_CURRENCY_ETH,
}),
}
}
}, [appliedNFTCreditsRaw, payAmount])

const formattedTotalAmount = React.useMemo(() => {
if (!payAmountRaw || !payAmount) return

if (!appliedNFTCreditsRaw) {
return {
primaryAmount: primaryAmount,
secondaryAmount: secondaryAmount,
}
}

const totalEth = payAmountRaw.eth.sub(appliedNFTCreditsRaw.eth)
const totalUsd = converter.weiToUsd(parseWad(totalEth))

const formattedEth = formatCurrencyAmount({
amount: fromWad(totalEth),
currency: V4_CURRENCY_ETH,
})
const formattedUsd = formatCurrencyAmount({
amount: fromWad(totalUsd),
currency: V4_CURRENCY_USD,
})

switch (payAmount?.currency) {
case V4_CURRENCY_ETH:
return {
primaryAmount: formattedEth,
secondaryAmount: formattedUsd,
}
case V4_CURRENCY_USD:
return {
primaryAmount: formattedUsd,
secondaryAmount: formattedEth,
}
}
}, [
appliedNFTCreditsRaw,
converter,
payAmount,
payAmountRaw,
primaryAmount,
secondaryAmount,
])

return {
formattedAmount: { primaryAmount, secondaryAmount },
formattedNftCredits: {
primaryAmount: formattedNftCredits?.primaryAmount,
secondaryAmount: formattedNftCredits?.secondaryAmount,
},
formattedTotalAmount: {
primaryAmount: formattedTotalAmount?.primaryAmount,
secondaryAmount: formattedTotalAmount?.secondaryAmount,
},
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from 'juice-sdk-react'
import { useProjectSelector } from 'packages/v4/components/ProjectDashboard/redux/hooks'
import { useV4NftRewards } from 'packages/v4/contexts/V4NftRewards/V4NftRewardsProvider'
import { useV4UserNftCredits } from 'packages/v4/contexts/V4UserNftCreditsProvider'
import { useProjectHasErc20Token } from 'packages/v4/hooks/useProjectHasErc20Token'
import { V4_CURRENCY_ETH } from 'packages/v4/utils/currency'
import { ProjectPayReceipt } from 'packages/v4/views/V4ProjectDashboard/hooks/useProjectPageQueries'
Expand Down Expand Up @@ -40,6 +41,7 @@ export const usePayProjectTx = ({
) => void
}) => {
const { userAddress } = useWallet()
const { data: nftCredits } = useV4UserNftCredits()
const { payAmount, chosenNftRewards } = useProjectSelector(
state => state.projectCart,
)
Expand Down Expand Up @@ -71,12 +73,21 @@ export const usePayProjectTx = ({
const weiAmount = useMemo(() => {
if (!payAmount) {
return 0n
} else if (payAmount.currency === V4_CURRENCY_ETH) {
return parseEther(payAmount.amount.toString())
} else {
return converter.usdToWei(payAmount.amount).toBigInt()
}
}, [payAmount, converter])
let weiAmount =
payAmount.currency === V4_CURRENCY_ETH
? parseEther(payAmount.amount.toString())
: converter.usdToWei(payAmount.amount).toBigInt()
if (nftCredits) {
if (nftCredits >= weiAmount) {
weiAmount = 0n
} else {
weiAmount -= nftCredits
}
}

return weiAmount
}, [converter, nftCredits, payAmount])

const {
rulesetMetadata: { data: rulesetMetadata },
Expand Down Expand Up @@ -104,7 +115,6 @@ export const usePayProjectTx = ({
formikHelpers: FormikHelpers<PayProjectModalFormValues>,
) => {
if (
!weiAmount ||
!contracts.primaryNativeTerminal.data ||
!userAddress ||
!values.userAcceptsTerms
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { CubeIcon } from '@heroicons/react/24/outline'
import { Trans } from '@lingui/macro'
import { Button } from 'antd'
import { useWallet } from 'hooks/Wallet'
import { formatEther } from 'juice-sdk-core'
import { useReadJb721TiersHookPayCreditsOf } from 'juice-sdk-react'
import { useV4UserNftCredits } from 'packages/v4/contexts/V4UserNftCreditsProvider'
import { useProjectPageQueries } from 'packages/v4/views/V4ProjectDashboard/hooks/useProjectPageQueries'

export function V4NftCreditsCallouts() {
const { setProjectPageTab } = useProjectPageQueries()
const { userAddress } = useWallet()
const { data: nftCredits } = useReadJb721TiersHookPayCreditsOf({
address: userAddress,
})
const { data: nftCredits } = useV4UserNftCredits()

if (!nftCredits || nftCredits <= 0n) {
return null
Expand Down
Loading

0 comments on commit 0bfe8e0

Please sign in to comment.