From af95cb46ea01a8ff5bcd0772b3033ce2b854c072 Mon Sep 17 00:00:00 2001 From: Johnny D Date: Tue, 11 Jun 2024 12:08:16 +1000 Subject: [PATCH] feat: payouts in funding cycle history (#4356) --- .../HistoricalConfigurationPanel.test.tsx | 17 +++ .../HistoricalConfigurationPanel.tsx | 16 ++- .../components/HistoricalCycle.tsx | 115 +++++++++++++++++ .../components/HistoricalPayoutsData.tsx | 60 +++++++++ .../components/HistorySubPanel.tsx | 116 +----------------- .../PayoutsTable/PayoutTableSettings.tsx | 2 + 6 files changed, 212 insertions(+), 114 deletions(-) create mode 100644 src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalCycle.tsx create mode 100644 src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalPayoutsData.tsx diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalConfigurationPanel.test.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalConfigurationPanel.test.tsx index dc1219d222..03cf38935f 100644 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalConfigurationPanel.test.tsx +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalConfigurationPanel.test.tsx @@ -4,6 +4,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { render, screen } from '@testing-library/react' +import { V2V3CurrencyOption } from 'models/v2v3/currencyOption' import { useHistoricalConfigurationPanel } from '../hooks/useConfigurationPanel/useHistoricalConfigurationPanel' import { HistoricalConfigurationPanel } from './HistoricalConfigurationPanel' @@ -42,6 +43,10 @@ describe('CurrentUpcomingConfigurationPanel', () => { , ) }) @@ -51,6 +56,10 @@ describe('CurrentUpcomingConfigurationPanel', () => { , ) expect(screen.getByTestId('configuration-panel')).toHaveTextContent( @@ -69,11 +78,19 @@ describe('CurrentUpcomingConfigurationPanel', () => { , ) expect(useHistoricalConfigurationPanel).toHaveBeenCalledWith({ fundingCycle: { id: '1' }, metadata: { id: '1' }, + withdrawnAmountAndCurrency: { + amount: 1, + currency: 1 as V2V3CurrencyOption, + }, }) }) }) diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalConfigurationPanel.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalConfigurationPanel.tsx index 8f74988fd1..01cdd8109d 100644 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalConfigurationPanel.tsx +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalConfigurationPanel.tsx @@ -1,18 +1,30 @@ +import { V2V3CurrencyOption } from 'models/v2v3/currencyOption' import { V2V3FundingCycle, V2V3FundingCycleMetadata, } from 'models/v2v3/fundingCycle' import { useHistoricalConfigurationPanel } from '../hooks/useConfigurationPanel/useHistoricalConfigurationPanel' import { ConfigurationPanel } from './ConfigurationPanel' +import { HistoricalPayoutsData } from './HistoricalPayoutsData' -type HistoricalConfigurationPanelProps = { +export type HistoricalConfigurationPanelProps = { fundingCycle: V2V3FundingCycle metadata: V2V3FundingCycleMetadata + withdrawnAmountAndCurrency: { + amount: number + currency: V2V3CurrencyOption + } } export const HistoricalConfigurationPanel: React.FC< HistoricalConfigurationPanelProps > = p => { const props = useHistoricalConfigurationPanel(p) - return + + return ( +
+ + +
+ ) } diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalCycle.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalCycle.tsx new file mode 100644 index 0000000000..4857224c0f --- /dev/null +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalCycle.tsx @@ -0,0 +1,115 @@ +import { Disclosure, Transition } from '@headlessui/react' +import { ChevronDownIcon } from '@heroicons/react/24/outline' +import { useProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' +import { BigNumber } from 'ethers' +import { FundingCyclesQuery } from 'generated/graphql' +import useProjectDistributionLimit from 'hooks/v2v3/contractReader/useProjectDistributionLimit' +import { V2V3CurrencyOption } from 'models/v2v3/currencyOption' +import moment from 'moment' +import React, { Fragment, useMemo } from 'react' +import { twMerge } from 'tailwind-merge' +import { isBigNumberish } from 'utils/bigNumbers' +import { formatCurrencyAmount } from 'utils/format/formatCurrencyAmount' +import { fromWad } from 'utils/format/formatNumber' +import { V2V3_CURRENCY_ETH } from 'utils/v2v3/currency' +import { + sgFCToV2V3FundingCycle, + sgFCToV2V3FundingCycleMetadata, +} from 'utils/v2v3/fundingCycle' +import { useProjectContext } from '../../../hooks/useProjectContext' +import { HistoricalConfigurationPanel } from './HistoricalConfigurationPanel' + +type QueriedFundingCycle = FundingCyclesQuery['fundingCycles'][number] + +const HistoricalCycle: React.FC = cycle => { + const { projectId } = useProjectMetadataContext() + const { primaryETHTerminal } = useProjectContext() + + const { data: distributionLimit } = useProjectDistributionLimit({ + projectId, + configuration: cycle.configuration.toString(), + terminal: primaryETHTerminal, + }) + const currency = + useMemo(() => { + if (typeof distributionLimit === 'undefined') return + + if ( + !(Array.isArray(distributionLimit) && distributionLimit.length === 2) + ) { + console.error( + 'Unexpected result from distributionLimitOf', + distributionLimit, + ) + throw new Error('Unexpected result from distributionLimitOf') + } + + const [, currency] = distributionLimit + + if (!isBigNumberish(currency)) { + console.error( + 'Unexpected result from distributionLimitOf', + distributionLimit, + ) + throw new Error('Unexpected result from distributionLimitOf') + } + const _currencyOption = BigNumber.from(currency).toNumber() + if (_currencyOption !== 0) return _currencyOption as V2V3CurrencyOption + }, [distributionLimit]) ?? V2V3_CURRENCY_ETH + + const withdrawnAmountAndCurrency = { + amount: parseFloat(fromWad(cycle.withdrawnAmount)), + currency: currency, + } + + return ( + + {({ open }) => ( +
+ +
#{cycle.number}
+
+ + {formatCurrencyAmount(withdrawnAmountAndCurrency) ?? '0'} + +
+
+ {`${moment( + (cycle.startTimestamp + cycle.duration) * 1000, + ).fromNow(true)} ago`} +
+
+ +
+
+ + + + + +
+ )} +
+ ) +} + +export default HistoricalCycle diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalPayoutsData.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalPayoutsData.tsx new file mode 100644 index 0000000000..41c4aac47c --- /dev/null +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistoricalPayoutsData.tsx @@ -0,0 +1,60 @@ +import EthereumAddress from 'components/EthereumAddress' +import { ETH_PAYOUT_SPLIT_GROUP } from 'constants/splits' +import { ProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' +import useProjectSplits from 'hooks/v2v3/contractReader/useProjectSplits' +import round from 'lodash/round' +import React, { useContext } from 'react' +import { formatCurrencyAmount } from 'utils/format/formatCurrencyAmount' +import { V2V3_CURRENCY_ETH } from 'utils/v2v3/currency' +import { derivePayoutAmount } from 'utils/v2v3/distributions' +import { ConfigurationPanelTableData } from './ConfigurationPanel' +import { ConfigurationTable } from './ConfigurationTable' +import { HistoricalConfigurationPanelProps } from './HistoricalConfigurationPanel' + +export const HistoricalPayoutsData: React.FC< + HistoricalConfigurationPanelProps +> = p => { + const { projectId } = useContext(ProjectMetadataContext) + + const config = p.fundingCycle?.configuration?.toString() + const { data: payoutSplits } = useProjectSplits({ + projectId, + splitGroup: ETH_PAYOUT_SPLIT_GROUP, + domain: config, + }) + + const currency = p.withdrawnAmountAndCurrency.currency + const withdrawnAmount = p.withdrawnAmountAndCurrency.amount + + const payoutSplitsData = + payoutSplits?.reduce((acc, split) => { + const key = split.beneficiary ?? '' + const payoutAmount = round( + derivePayoutAmount({ + payoutSplit: split, + distributionLimit: withdrawnAmount, + }), + currency === V2V3_CURRENCY_ETH ? 4 : 2, + ) + + const formattedPayout = + formatCurrencyAmount({ + amount: payoutAmount, + currency, + }) ?? '0' + + acc[key] = { + name: , + new: {formattedPayout}, + } + return acc + }, {}) ?? {} + + return ( + <> + {payoutSplits && payoutSplits.length ? ( + + ) : null} + + ) +} diff --git a/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistorySubPanel.tsx b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistorySubPanel.tsx index 9396f2f135..eb7d535136 100644 --- a/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistorySubPanel.tsx +++ b/src/components/v2v3/V2V3Project/ProjectDashboard/components/CyclesPayoutsPanel/components/HistorySubPanel.tsx @@ -1,24 +1,11 @@ -import { Disclosure, Transition } from '@headlessui/react' -import { ChevronDownIcon } from '@heroicons/react/24/outline' import { Trans, t } from '@lingui/macro' import { Button } from 'antd' import { useProjectContext } from 'components/v2v3/V2V3Project/ProjectDashboard/hooks/useProjectContext' import { useProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' -import { BigNumber } from 'ethers' -import useProjectDistributionLimit from 'hooks/v2v3/contractReader/useProjectDistributionLimit' -import { V2V3CurrencyOption } from 'models/v2v3/currencyOption' -import moment from 'moment' -import { Fragment, useMemo, useState } from 'react' +import { useState } from 'react' import { twMerge } from 'tailwind-merge' -import { isBigNumberish } from 'utils/bigNumbers' -import { formatCurrencyAmount } from 'utils/format/formatCurrencyAmount' -import { fromWad } from 'utils/format/formatNumber' -import { - sgFCToV2V3FundingCycle, - sgFCToV2V3FundingCycleMetadata, -} from 'utils/v2v3/fundingCycle' import { usePastFundingCycles } from '../hooks/usePastFundingCycles' -import { HistoricalConfigurationPanel } from './HistoricalConfigurationPanel' +import HistoricalCycle from './HistoricalCycle' export const HistorySubPanel = () => { const { projectId } = useProjectMetadataContext() @@ -60,50 +47,8 @@ export const HistorySubPanel = () => {
{error.message}
) : ( <> - {data?.fundingCycles.map(cycle => ( - - {({ open }) => ( -
- -
#{cycle.number}
-
- -
-
- {`${moment( - (cycle.startTimestamp + cycle.duration) * 1000, - ).fromNow(true)} ago`} -
-
- -
-
- - - - - -
- )} -
+ {data?.fundingCycles.map((cycle, index) => ( + ))} {isLoading ? ( @@ -159,56 +104,3 @@ const SkeletonRow = () => ( ) - -/** - * Component to get the currency for a specific funding cycle, and render its formatted withdrawnAmount in that currency - */ -function FormattedWithdrawnAmount({ - configuration, - withdrawnAmount, -}: { - configuration: BigNumber - withdrawnAmount: BigNumber -}) { - const { projectId } = useProjectMetadataContext() - const { primaryETHTerminal } = useProjectContext() - - const { data: distributionLimit } = useProjectDistributionLimit({ - projectId, - configuration: configuration.toString(), - terminal: primaryETHTerminal, - }) - - const currencyOption = useMemo(() => { - if (typeof distributionLimit === 'undefined') return - - if (!(Array.isArray(distributionLimit) && distributionLimit.length === 2)) { - console.error( - 'Unexpected result from distributionLimitOf', - distributionLimit, - ) - throw new Error('Unexpected result from distributionLimitOf') - } - - const [, currency] = distributionLimit - - if (!isBigNumberish(currency)) { - console.error( - 'Unexpected result from distributionLimitOf', - distributionLimit, - ) - throw new Error('Unexpected result from distributionLimitOf') - } - const _currencyOption = BigNumber.from(currency).toNumber() - if (_currencyOption !== 0) return _currencyOption as V2V3CurrencyOption - }, [distributionLimit]) - - return ( - - {formatCurrencyAmount({ - amount: fromWad(withdrawnAmount), - currency: currencyOption, - }) ?? '0Ξ'} - - ) -} diff --git a/src/components/v2v3/shared/PayoutsTable/PayoutTableSettings.tsx b/src/components/v2v3/shared/PayoutsTable/PayoutTableSettings.tsx index a08ca44f40..7e3c045f57 100644 --- a/src/components/v2v3/shared/PayoutsTable/PayoutTableSettings.tsx +++ b/src/components/v2v3/shared/PayoutsTable/PayoutTableSettings.tsx @@ -23,11 +23,13 @@ export function PayoutTableSettings() { distributionLimitIsInfinite, handleDeleteAllPayoutSplits, setDistributionLimit, + setCurrency, setSplits100Percent, } = usePayoutsTable() const handleSwitchToLimitedPayouts = (newLimit: ReduxDistributionLimit) => { setDistributionLimit(parseFloat(fromWad(newLimit.amount))) + setCurrency(newLimit.currency) setSwitchToLimitedModalOpen(false) }