From ece53533fbbdfb3147bdca9daa24456041642acb Mon Sep 17 00:00:00 2001 From: Johnny D Date: Wed, 28 Aug 2024 14:58:57 +0800 Subject: [PATCH] V4 edit cycle: Review and Deploy modal [1/n] (#4440) --- .../shared => components}/DiffedItem.tsx | 0 .../FundingCycleDetailsRow.tsx | 0 .../FundingCycleListItem.tsx | 2 +- src/locales/messages.pot | 3 + .../DataSourceListItems/index.tsx | 2 +- .../FundingCycleListItems/DurationValue.tsx | 1 + .../FundingCycleListItems/index.tsx | 2 +- .../RulesListItems/index.tsx | 2 +- .../TokenListItems/index.tsx | 2 +- .../FundingCycleDetails/index.tsx | 2 +- .../ReviewConfirmModal/DetailsSectionDiff.tsx | 2 +- .../ReviewConfirmModal/PayoutsSectionDiff.tsx | 2 +- .../ReviewConfirmModal/TokensSectionDiff.tsx | 2 +- .../DiffedJBProjectBeneficiary.tsx | 2 +- .../DiffedSplitFields/DiffedLockedUntil.tsx | 2 +- .../DiffedSplitFields/DiffedSplitValue.tsx | 2 +- .../shared/DiffedSplits/DiffedSplitItem.tsx | 2 +- src/packages/v4/utils/fundingCycle.ts | 3 + src/packages/v4/utils/v4Splits.ts | 4 +- .../EditCyclePage/EditCyclePage.tsx | 5 +- .../ReviewConfirmModal/DetailsSectionDiff.tsx | 145 +++++++++++++++++ .../ReviewConfirmModal/DiffSection.tsx | 23 +++ .../DiffedJBProjectBeneficiary.tsx | 34 ++++ .../DiffedSplitFields/DiffedLockedUntil.tsx | 72 +++++++++ .../DiffedSplitFields/DiffedSplitValue.tsx | 70 +++++++++ .../DiffedSplits/DiffedSplitItem.tsx | 75 +++++++++ .../DiffedSplits/DiffedSplitList.tsx | 115 ++++++++++++++ .../FormattedRulesetValues/AllowedValue.tsx | 5 + .../DetailsSection/ApprovalStrategyValue.tsx | 27 ++++ .../DetailsSection/DurationValue.tsx | 32 ++++ .../DetailsSection/PayoutLimitValue.tsx | 70 +++++++++ .../Tokens/IssuanceRateValue.tsx | 22 +++ .../ReviewConfirmModal/PayoutsSectionDiff.tsx | 97 ++++++++++++ .../ReviewConfirmModal/ReviewConfirmModal.tsx | 146 ++++++++++++++++++ .../SectionCollapseHeader.tsx | 28 ++++ .../ReviewConfirmModal/TokensSectionDiff.tsx | 144 +++++++++++++++++ .../hooks/useDetailsSectionValues.ts | 95 ++++++++++++ .../hooks/usePayoutsSectionValues.ts | 70 +++++++++ .../hooks/useTokensSectionValues.ts | 122 +++++++++++++++ .../ReviewConfirmModal/index.tsx | 1 + .../EditCyclePage/TransactionSuccessModal.tsx | 57 +++++++ src/utils/format/formatTime.ts | 2 +- 42 files changed, 1476 insertions(+), 18 deletions(-) rename src/{packages/v2v3/components/shared => components}/DiffedItem.tsx (100%) rename src/{packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails => components}/FundingCycleDetailsRow.tsx (100%) rename src/{packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails => components}/FundingCycleListItem.tsx (96%) create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DetailsSectionDiff.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffSection.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedJBProjectBeneficiary.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedLockedUntil.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedSplitValue.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitItem.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitList.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/AllowedValue.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/ApprovalStrategyValue.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/DurationValue.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/PayoutLimitValue.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/Tokens/IssuanceRateValue.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/PayoutsSectionDiff.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/SectionCollapseHeader.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/useDetailsSectionValues.ts create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/usePayoutsSectionValues.ts create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/useTokensSectionValues.ts create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/index.tsx create mode 100644 src/packages/v4/views/V4ProjectSettings/EditCyclePage/TransactionSuccessModal.tsx diff --git a/src/packages/v2v3/components/shared/DiffedItem.tsx b/src/components/DiffedItem.tsx similarity index 100% rename from src/packages/v2v3/components/shared/DiffedItem.tsx rename to src/components/DiffedItem.tsx diff --git a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleDetailsRow.tsx b/src/components/FundingCycleDetailsRow.tsx similarity index 100% rename from src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleDetailsRow.tsx rename to src/components/FundingCycleDetailsRow.tsx diff --git a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItem.tsx b/src/components/FundingCycleListItem.tsx similarity index 96% rename from src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItem.tsx rename to src/components/FundingCycleListItem.tsx index 94844e1586..4cb8016a0c 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItem.tsx +++ b/src/components/FundingCycleListItem.tsx @@ -1,7 +1,7 @@ import { Tooltip } from 'antd' import { twMerge } from 'tailwind-merge' import { classNames } from 'utils/classNames' -import { DiffedItem } from '../../../shared/DiffedItem' +import { DiffedItem } from './DiffedItem' // e.g. 'Distribution limit', 'Start', 'End', etc. export function FundingCycleListItem({ diff --git a/src/locales/messages.pot b/src/locales/messages.pot index 02528e82ed..1dfce92901 100644 --- a/src/locales/messages.pot +++ b/src/locales/messages.pot @@ -3611,6 +3611,9 @@ msgstr "" msgid "Owner tools" msgstr "" +msgid "{value} {tokenSymbol}/ETH" +msgstr "" + msgid "<0>Juicebox is a programmable crypto fundraising platform for web3. It helps people fund, operate, and scale their projects transparently using Ethereum, which is a type of programmable cryptocurrency.<1>Juicebox is funded and owned by its community." msgstr "" diff --git a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/DataSourceListItems/index.tsx b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/DataSourceListItems/index.tsx index fa2ee206eb..2962b62cb6 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/DataSourceListItems/index.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/DataSourceListItems/index.tsx @@ -3,6 +3,7 @@ import { Trans, t } from '@lingui/macro' import { Tooltip } from 'antd' import EthereumAddress from 'components/EthereumAddress' import ExternalLink from 'components/ExternalLink' +import { FundingCycleListItem } from 'components/FundingCycleListItem' import { DATASOURCE_EXPLANATION, NFT_DATASOURCE_EXPLANATION, @@ -16,7 +17,6 @@ import { V2V3FundingCycleMetadata } from 'packages/v2v3/models/fundingCycle' import { useContext } from 'react' import { formatBoolean } from 'utils/format/formatBoolean' import { helpPagePath } from 'utils/helpPagePath' -import { FundingCycleListItem } from '../FundingCycleListItem' function DataSourceAddressValue({ address }: { address: string | undefined }) { const { version } = useContext(JB721DelegateContractsContext) diff --git a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/DurationValue.tsx b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/DurationValue.tsx index 878f96d910..246eed3195 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/DurationValue.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/DurationValue.tsx @@ -1,3 +1,4 @@ + import { Trans } from '@lingui/macro' import FundingCycleDetailWarning from 'components/Project/FundingCycleDetailWarning' import { FUNDING_CYCLE_WARNING_TEXT } from 'constants/fundingWarningText' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/index.tsx b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/index.tsx index 9884e1891b..6b6ca096d7 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/index.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/index.tsx @@ -1,5 +1,6 @@ import { t } from '@lingui/macro' import { Tooltip } from 'antd' +import { FundingCycleListItem } from 'components/FundingCycleListItem' import { DISTRIBUTION_LIMIT_EXPLANATION, RECONFIG_RULES_EXPLANATION, @@ -17,7 +18,6 @@ import { V2V3CurrencyName } from 'packages/v2v3/utils/currency' import { getUnsafeV2V3FundingCycleProperties } from 'packages/v2v3/utils/fundingCycle' import { useContext } from 'react' import { formatDate, formatDateToUTC } from 'utils/format/formatDate' -import { FundingCycleListItem } from '../FundingCycleListItem' import { BallotStrategyValue } from '../RulesListItems/BallotStrategyValue' import { DistributionLimitValue } from './DistributionLimitValue' import { DurationValue } from './DurationValue' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/RulesListItems/index.tsx b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/RulesListItems/index.tsx index eb26bfffda..f97b8a806f 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/RulesListItems/index.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/RulesListItems/index.tsx @@ -1,4 +1,5 @@ import { t } from '@lingui/macro' +import { FundingCycleListItem } from 'components/FundingCycleListItem' import { CONTROLLER_CONFIG_EXPLANATION, CONTROLLER_MIGRATION_EXPLANATION, @@ -9,7 +10,6 @@ import { import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext' import { V2V3FundingCycleMetadata } from 'packages/v2v3/models/fundingCycle' import { useContext } from 'react' -import { FundingCycleListItem } from '../FundingCycleListItem' import { AllowedValue } from './AllowedValue' import { HoldFeesValue } from './HoldFeesValue' import { PausePayValue } from './PausePayValue' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/TokenListItems/index.tsx b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/TokenListItems/index.tsx index ef254ad3c8..34737d7a0d 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/TokenListItems/index.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/TokenListItems/index.tsx @@ -1,4 +1,5 @@ import { t } from '@lingui/macro' +import { FundingCycleListItem } from 'components/FundingCycleListItem' import { CONTRIBUTOR_RATE_EXPLANATION, DISCOUNT_RATE_EXPLANATION, @@ -13,7 +14,6 @@ import { V2V3FundingCycle, V2V3FundingCycleMetadata, } from 'packages/v2v3/models/fundingCycle' -import { FundingCycleListItem } from '../FundingCycleListItem' import { BigNumber } from 'ethers' import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/index.tsx b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/index.tsx index 0aecb20bef..610d580e0d 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/index.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/index.tsx @@ -5,9 +5,9 @@ import { V2V3FundingCycleMetadata, } from 'packages/v2v3/models/fundingCycle' +import { FundingCycleDetailsRow } from 'components/FundingCycleDetailsRow' import { isZeroAddress } from 'utils/address' import { DataSourceListItems } from './DataSourceListItems' -import { FundingCycleDetailsRow } from './FundingCycleDetailsRow' import { FundingCycleListItems } from './FundingCycleListItems' import { RulesListItems } from './RulesListItems' import { TokenListItems } from './TokenListItems' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/DetailsSectionDiff.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/DetailsSectionDiff.tsx index 695c7dac8f..f24754dd42 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/DetailsSectionDiff.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/DetailsSectionDiff.tsx @@ -1,5 +1,5 @@ import { Trans, t } from '@lingui/macro' -import { FundingCycleListItem } from 'packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItem' +import { FundingCycleListItem } from 'components/FundingCycleListItem' import { DurationValue } from 'packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/DurationValue' import { BallotStrategyValue } from 'packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/RulesListItems/BallotStrategyValue' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/PayoutsSectionDiff.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/PayoutsSectionDiff.tsx index 91b53030c4..1b0e488e76 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/PayoutsSectionDiff.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/PayoutsSectionDiff.tsx @@ -1,6 +1,6 @@ import { BigNumber } from '@ethersproject/bignumber' import { Trans, t } from '@lingui/macro' -import { FundingCycleListItem } from 'packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItem' +import { FundingCycleListItem } from 'components/FundingCycleListItem' import { DistributionLimitValue } from 'packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItems/DistributionLimitValue' import DiffedSplitList from 'packages/v2v3/components/shared/DiffedSplits/DiffedSplitList' import { getV2V3CurrencyOption } from 'packages/v2v3/utils/currency' diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx index cfd026ec1f..17f13f2df9 100644 --- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx +++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx @@ -1,6 +1,6 @@ import { BigNumber } from '@ethersproject/bignumber' import { Trans, t } from '@lingui/macro' -import { FundingCycleListItem } from 'packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/FundingCycleListItem' +import { FundingCycleListItem } from 'components/FundingCycleListItem' import { MintRateValue } from 'packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/TokenListItems/MintRateValue' import { ReservedRateValue } from 'packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/TokenListItems/ReservedRateValue' import DiffedSplitList from 'packages/v2v3/components/shared/DiffedSplits/DiffedSplitList' diff --git a/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedJBProjectBeneficiary.tsx b/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedJBProjectBeneficiary.tsx index a35b46c54d..5673895b66 100644 --- a/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedJBProjectBeneficiary.tsx +++ b/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedJBProjectBeneficiary.tsx @@ -1,6 +1,6 @@ +import { DiffedItem } from 'components/DiffedItem' import EthereumAddress from 'components/EthereumAddress' import { Split } from 'packages/v2v3/models/splits' -import { DiffedItem } from '../../DiffedItem' import { JuiceboxProjectBeneficiary } from '../../SplitItem/JuiceboxProjectBeneficiary' export function DiffedJBProjectBeneficiary({ diff --git a/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedLockedUntil.tsx b/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedLockedUntil.tsx index 627c893024..41506aba01 100644 --- a/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedLockedUntil.tsx +++ b/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedLockedUntil.tsx @@ -1,5 +1,5 @@ +import { DiffedItem } from 'components/DiffedItem' import { formatDate } from 'utils/format/formatDate' -import { DiffedItem } from '../../DiffedItem' import { LockedUntilValue } from '../../SplitItem/LockedUntilValue' export function DiffedLockedUntil({ diff --git a/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedSplitValue.tsx b/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedSplitValue.tsx index d81a2d4d80..c71e95bbdb 100644 --- a/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedSplitValue.tsx +++ b/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitFields/DiffedSplitValue.tsx @@ -1,3 +1,4 @@ +import { DiffedItem } from 'components/DiffedItem' import { BigNumber } from 'ethers' import { Split } from 'packages/v2v3/models/splits' import { @@ -6,7 +7,6 @@ import { } from 'packages/v2v3/utils/fundingCycle' import { formatSplitPercent } from 'packages/v2v3/utils/math' import { splitAmountsAreEqual } from 'packages/v2v3/utils/v2v3Splits' -import { DiffedItem } from '../../DiffedItem' import { SplitProps } from '../../SplitItem' import { SplitAmountValue } from '../../SplitItem/SplitAmountValue' diff --git a/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitItem.tsx b/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitItem.tsx index 59185189ec..962d66b6ed 100644 --- a/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitItem.tsx +++ b/src/packages/v2v3/components/shared/DiffedSplits/DiffedSplitItem.tsx @@ -3,7 +3,7 @@ import { DIFF_OLD_BACKGROUND, DiffMinus, DiffPlus, -} from 'packages/v2v3/components/shared/DiffedItem' +} from 'components/DiffedItem' import { isJuiceboxProjectSplit } from 'packages/v2v3/utils/distributions' import { SplitWithDiff } from 'packages/v2v3/utils/v2v3Splits' import { twMerge } from 'tailwind-merge' diff --git a/src/packages/v4/utils/fundingCycle.ts b/src/packages/v4/utils/fundingCycle.ts index 52c8e8e887..c501963098 100644 --- a/src/packages/v4/utils/fundingCycle.ts +++ b/src/packages/v4/utils/fundingCycle.ts @@ -1,5 +1,8 @@ import { MAX_PAYOUT_LIMIT } from "./math" +export const WEIGHT_ZERO = 1 // send `1` when we want to set the weight to `0` +export const WEIGHT_UNCHANGED = 0 // send `0` when we don't want to change the weight. + export function isInfinitePayoutLimit( payoutLimit: bigint | undefined, ) { diff --git a/src/packages/v4/utils/v4Splits.ts b/src/packages/v4/utils/v4Splits.ts index 9e4f413ee6..ae49175246 100644 --- a/src/packages/v4/utils/v4Splits.ts +++ b/src/packages/v4/utils/v4Splits.ts @@ -44,7 +44,7 @@ export const totalSplitsPercent = (splits: JBSplit[]): bigint => // - false if new (exists in new but not old) // - JBSplit if exists in old and new and there is a diff in the splits // - undefined if exists in old and new and there is no diff in the splits -type OldSplit = JBSplit | boolean | undefined +type OldSplit = (JBSplit & { totalValue?: bigint } ) | boolean | undefined export type SplitWithDiff = JBSplit & { oldSplit?: OldSplit @@ -99,7 +99,7 @@ export const sortSplits = (splits: JBSplit[]) => { } /* Determines if two splits AMOUNTS are equal. Extracts amounts for two splits from their respective totalValues **/ -function splitAmountsAreEqual({ +export function splitAmountsAreEqual({ split1, split2, split1TotalValue, diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCyclePage.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCyclePage.tsx index ecb699c71f..ce1bc969c5 100644 --- a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCyclePage.tsx +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCyclePage.tsx @@ -13,6 +13,7 @@ import { useChainId } from 'wagmi' import { DetailsSection } from './DetailsSection' import { useEditCycleFormContext } from './EditCycleFormContext' import { PayoutsSection } from './PayoutsSection' +import { ReviewConfirmModal } from './ReviewConfirmModal' import { TokensSection } from './TokensSection' export function EditCyclePage() { @@ -116,10 +117,10 @@ export function EditCyclePage() { > - {/* setConfirmModalOpen(false)} - /> */} + /> diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DetailsSectionDiff.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DetailsSectionDiff.tsx new file mode 100644 index 0000000000..8634115038 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DetailsSectionDiff.tsx @@ -0,0 +1,145 @@ +import { Trans, t } from '@lingui/macro' + +import { FundingCycleListItem } from 'components/FundingCycleListItem' +import { DiffSection } from './DiffSection' +import { ApprovalStrategyValue } from './FormattedRulesetValues/DetailsSection/ApprovalStrategyValue' +import { DurationValue } from './FormattedRulesetValues/DetailsSection/DurationValue' +import { useDetailsSectionValues } from './hooks/useDetailsSectionValues' + +export const emptySectionClasses = 'text-sm text-secondary pt-2 pb-3' + +export function DetailsSectionDiff() { + const { + advancedOptionsHasDiff, + sectionHasDiff, + + currentDuration, + newDuration, + durationHasDiff, + + currentBallot, + newBallot, + ballotHasDiff, + + newPausePay, + currentPausePay, + pausePayHasDiff, + + newSetTerminals, + currentSetTerminals, + allowSetTerminalsHasDiff, + + newAllowTerminalMigration, + currentAllowTerminalMigration, + allowTerminalMigrationHasDiff, + + newSetController, + currentSetController, + allowSetControllerHasDiff, + } = useDetailsSectionValues() + + if (!sectionHasDiff) { + return ( +
+ No edits were made to cycle details for this cycle. +
+ ) + } + + return ( + + {durationHasDiff && ( + } + oldValue={} + /> + )} + {ballotHasDiff && currentBallot && ( + + } + oldValue={ + + } + /> + )} + + } + advancedOptions={ + advancedOptionsHasDiff && ( + <> + {pausePayHasDiff && ( + {newPausePay.toString()} + } + oldValue={ + + {currentPausePay.toString()} + + } + /> + )} + {allowSetTerminalsHasDiff && ( + + {newSetTerminals.toString()} + + } + oldValue={ + + {currentSetTerminals.toString()} + + } + /> + )} + {allowTerminalMigrationHasDiff && ( + + {newAllowTerminalMigration.toString()} + + } + oldValue={ + + {currentAllowTerminalMigration.toString()} + + } + /> + )} + {allowSetControllerHasDiff && ( + + {newSetController.toString()} + + } + oldValue={ + + {currentSetController.toString()} + + } + /> + )} + + ) + } + /> + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffSection.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffSection.tsx new file mode 100644 index 0000000000..69f08c39aa --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffSection.tsx @@ -0,0 +1,23 @@ +import { Trans } from '@lingui/macro' + +export function DiffSection({ + content, + advancedOptions, +}: { + content: React.ReactNode + advancedOptions?: React.ReactNode +}) { + return ( +
+ {content} + {advancedOptions ? ( + <> +
+ Advanced options: +
+ {advancedOptions} + + ) : null} +
+ ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedJBProjectBeneficiary.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedJBProjectBeneficiary.tsx new file mode 100644 index 0000000000..404811f91c --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedJBProjectBeneficiary.tsx @@ -0,0 +1,34 @@ +import { DiffedItem } from 'components/DiffedItem' +import EthereumAddress from 'components/EthereumAddress' +import { JBSplit } from 'juice-sdk-core' +import { JuiceboxProjectBeneficiary } from 'packages/v4/components/SplitList/SplitItem/JuiceboxProjectBeneficiary' + +export function DiffedJBProjectBeneficiary({ + split, + oldSplit, +}: { + split: JBSplit + oldSplit?: JBSplit +}) { + const hasDiff = oldSplit && oldSplit.beneficiary !== split.beneficiary + + return ( + + } + diffStatus="old" + /> + } + diffStatus="new" + /> + + ) : undefined + } + /> + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedLockedUntil.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedLockedUntil.tsx new file mode 100644 index 0000000000..85ee77a65b --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedLockedUntil.tsx @@ -0,0 +1,72 @@ +import { DiffedItem } from 'components/DiffedItem' +import { LockedUntilValue } from 'packages/v4/components/SplitList/SplitItem/LockedUntilValue' +import { formatDate } from 'utils/format/formatDate' + +export function DiffedLockedUntil({ + lockedUntil, + oldLockedUntil, +}: { + lockedUntil: number | undefined + oldLockedUntil?: number +}) { + const lockedUntilFormatted = lockedUntil + ? formatDate(lockedUntil * 1000, 'yyyy-MM-DD') + : undefined + const oldLockedUntilFormatted = oldLockedUntil + ? formatDate(oldLockedUntil * 1000, 'yyyy-MM-DD') + : undefined + + const hasOldLockedUntil = + oldLockedUntilFormatted && oldLockedUntil && oldLockedUntil > 0 + const hasNewLockedUntil = lockedUntil && lockedUntil > 0 + const hasDiff = lockedUntil !== oldLockedUntil + + if (!hasNewLockedUntil && !hasOldLockedUntil) return null + + if (hasDiff && !hasOldLockedUntil) { + // whole lockedUntil section green + return ( +
+ } + diffStatus="new" + /> +
+ ) + } + + if (hasDiff && hasOldLockedUntil) { + if (!hasNewLockedUntil) { + // whole lockedUntil section red + return ( +
+ } + diffStatus="old" + /> +
+ ) + } + + return ( + + {/* Diff within the lockedUntil section */} + {hasDiff && lockedUntilFormatted ? ( + <> + + + + ) : ( + // lockedUntil no diff + lockedUntilFormatted + )} + + } + /> + ) + } + + return +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedSplitValue.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedSplitValue.tsx new file mode 100644 index 0000000000..0fdc905254 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitFields/DiffedSplitValue.tsx @@ -0,0 +1,70 @@ +import { DiffedItem } from 'components/DiffedItem' +import { JBSplit } from 'juice-sdk-core' + +import { SplitProps } from 'packages/v4/components/SplitList/SplitItem' +import { SplitAmountValue } from 'packages/v4/components/SplitList/SplitItem/SplitAmountValue' +import { isFinitePayoutLimit, isInfinitePayoutLimit } from 'packages/v4/utils/fundingCycle' +import { splitAmountsAreEqual } from 'packages/v4/utils/v4Splits' + +export function DiffedSplitValue({ + splitProps, + diffSplit, +}: { + splitProps: SplitProps + diffSplit?: JBSplit & { totalValue?: bigint } +}) { + const diffSplitProps: SplitProps | undefined = diffSplit + ? { + split: diffSplit, + totalValue: diffSplit.totalValue, + projectOwnerAddress: splitProps.projectOwnerAddress, + currency: splitProps.currency, + } + : undefined + + const newValue = isInfinitePayoutLimit(splitProps?.totalValue) ? ( + <>{splitProps.split.percent.formatPercentage()}% + ) : ( + + ) + + if (!diffSplitProps) return newValue + + const oldValue = + diffSplit && isInfinitePayoutLimit(diffSplit?.totalValue) ? ( + <>{diffSplit.percent.formatPercentage()}% + ) : ( + + ) + + const isFiniteTotalValue = + isFinitePayoutLimit(splitProps.totalValue) && + isFinitePayoutLimit(diffSplitProps.totalValue) + + const percentsEqual = + splitProps.split.percent === diffSplitProps.split.percent + + const valuesEqual = isFiniteTotalValue + ? splitAmountsAreEqual({ + split1: splitProps.split, + split2: diffSplitProps.split, + split1TotalValue: splitProps.totalValue ?? 0n, + split2TotalValue: diffSplitProps.totalValue ?? 0n, + }) + : percentsEqual + + if (valuesEqual) return
{newValue}
+ + return ( +
+ + +
+ ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitItem.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitItem.tsx new file mode 100644 index 0000000000..a0096a0422 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitItem.tsx @@ -0,0 +1,75 @@ +import { + DIFF_NEW_BACKGROUND, + DIFF_OLD_BACKGROUND, + DiffMinus, + DiffPlus, +} from 'components/DiffedItem' +import { SplitProps } from 'packages/v4/components/SplitList/SplitItem' +import { ETHAddressBeneficiary } from 'packages/v4/components/SplitList/SplitItem/EthAddressBeneficiary' +import { isJuiceboxProjectSplit, SplitWithDiff } from 'packages/v4/utils/v4Splits' +import { twMerge } from 'tailwind-merge' +import { DiffedJBProjectBeneficiary } from './DiffedSplitFields/DiffedJBProjectBeneficiary' +import { DiffedLockedUntil } from './DiffedSplitFields/DiffedLockedUntil' +import { DiffedSplitValue } from './DiffedSplitFields/DiffedSplitValue' + +type DiffedSplitProps = Omit & { split: SplitWithDiff } + +export function DiffedSplitItem({ props }: { props: DiffedSplitProps }) { + const isJuiceboxProject = isJuiceboxProjectSplit(props.split) + + const oldSplit = props.split.oldSplit + const splitIsRemoved = oldSplit === true + const splitIsNew = oldSplit === false + + const hasDiff = oldSplit !== undefined && !(splitIsRemoved || splitIsNew) + + const className = twMerge( + 'flex flex-wrap items-center justify-between py-1 rounded-md', + splitIsRemoved ? DIFF_OLD_BACKGROUND : undefined, + splitIsNew ? DIFF_NEW_BACKGROUND : undefined, + splitIsRemoved || splitIsNew ? 'pl-3 pr-4 my-1' : undefined, + ) + + return ( +
+
+
+ {splitIsRemoved ? : null} + {splitIsNew ? : null} + {isJuiceboxProject ? ( + + ) : ( + + )} +
+ + {(props.split.lockedUntil && props.split.lockedUntil > 0) || hasDiff ? ( + + ) : null} +
+
+ + {/* '?' icon that appears next reserved percents */} + {/* {props.reservedPercent !== undefined ? ( + + ) : null} */} +
+
+ ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitList.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitList.tsx new file mode 100644 index 0000000000..dd18f15c57 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/DiffedSplits/DiffedSplitList.tsx @@ -0,0 +1,115 @@ +import { JBSplit as Split } from 'juice-sdk-core' +import round from 'lodash/round' + +import { SplitProps } from 'packages/v4/components/SplitList/SplitItem' +import useProjectOwnerOf from 'packages/v4/hooks/useV4ProjectOwnerOf' +import { processUniqueSplits, v4GetProjectOwnerRemainderSplit } from 'packages/v4/utils/v4Splits' +import { useMemo } from 'react' +import { DiffedSplitItem } from './DiffedSplitItem' + +const JB_PERCENT_PRECISION = 2 + +type DiffedSplitListProps = { + splits: Split[] + diffSplits?: Split[] + currency?: bigint + oldCurrency?: bigint + totalValue: bigint | undefined + oldTotalValue?: bigint + previousTotalValue?: bigint + showFees?: boolean + valueSuffix?: string | JSX.Element + valueFormatProps?: { precision?: number } + reservedPercent?: number + showDiffs?: boolean +} + +export default function DiffedSplitList({ + splits, + diffSplits, + showFees = false, + currency, + oldCurrency, + totalValue, + previousTotalValue, + valueSuffix, + valueFormatProps, + reservedPercent, + showDiffs, +}: DiffedSplitListProps) { + const { data: projectOwnerAddress } = useProjectOwnerOf() + const ownerSplit = useMemo(() => { + if (!projectOwnerAddress) return + return v4GetProjectOwnerRemainderSplit(projectOwnerAddress, splits) + }, [projectOwnerAddress, splits]) + + const diffOwnerSplit = useMemo(() => { + if (!diffSplits || !projectOwnerAddress || !showDiffs) return + return v4GetProjectOwnerRemainderSplit(projectOwnerAddress, diffSplits) + }, [projectOwnerAddress, diffSplits, showDiffs]) + + const ownerSplitIsRemoved = + !ownerSplit?.percent && diffOwnerSplit?.percent.value === 0n + + const roundedDiffOwnerSplitPercent = round((diffOwnerSplit?.percent.formatPercentage() || 0), + JB_PERCENT_PRECISION, + ) + const diffOwnerSplitHasPercent = + diffOwnerSplit && roundedDiffOwnerSplitPercent !== 0 + + const ownerSplitIsNew = ownerSplit?.percent && !diffOwnerSplitHasPercent + + const currencyHasDiff = Boolean( + oldCurrency && currency && (oldCurrency !== currency), + ) + const uniqueSplits = processUniqueSplits({ + oldTotalValue: previousTotalValue, + newTotalValue: totalValue, + allSplitsChanged: currencyHasDiff, + oldSplits: showDiffs ? diffSplits : undefined, + newSplits: splits, + }) + + const splitProps: Omit = { + currency, + oldCurrency, + totalValue, + projectOwnerAddress, + valueSuffix, + valueFormatProps, + reservedPercent, + showFee: showFees, + } + return ( +
+ {uniqueSplits.map(split => { + const splitIsRemoved = split.oldSplit === true + return ( + + ) + })} + {ownerSplit?.percent ? ( + + ) : null} +
+ ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/AllowedValue.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/AllowedValue.tsx new file mode 100644 index 0000000000..1da80dfa51 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/AllowedValue.tsx @@ -0,0 +1,5 @@ +import { formatAllowed } from 'utils/format/formatBoolean' + +export function AllowedValue({ value }: { value: boolean | undefined }) { + return <>{formatAllowed(value)} +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/ApprovalStrategyValue.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/ApprovalStrategyValue.tsx new file mode 100644 index 0000000000..1065300b86 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/ApprovalStrategyValue.tsx @@ -0,0 +1,27 @@ +import { Tooltip } from 'antd' +import EtherscanLink from 'components/EtherscanLink' +import FundingCycleDetailWarning from 'components/Project/FundingCycleDetailWarning' +import { BallotStrategy } from 'models/ballot' + +export function ApprovalStrategyValue({ + approvalStrategy, + warningText, +}: { + approvalStrategy: BallotStrategy + warningText: string | undefined +}) { + return ( + + } + > + + {approvalStrategy.name} + + + + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/DurationValue.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/DurationValue.tsx new file mode 100644 index 0000000000..51b0c10917 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/DurationValue.tsx @@ -0,0 +1,32 @@ +import { Trans } from '@lingui/macro' +import FundingCycleDetailWarning from 'components/Project/FundingCycleDetailWarning' +import { FUNDING_CYCLE_WARNING_TEXT } from 'constants/fundingWarningText' +import { detailedTimeString } from 'utils/format/formatTime' + +export function DurationValue({ + duration, +}: { + duration: number | undefined +}) { + const formattedDuration = duration + ? detailedTimeString({ + timeSeconds: duration, + fullWords: true, + }) + : undefined + const riskWarningText = FUNDING_CYCLE_WARNING_TEXT() + return ( + <> + {duration && duration > 0 ? ( + formattedDuration + ) : ( + + Not set + + )} + + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/PayoutLimitValue.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/PayoutLimitValue.tsx new file mode 100644 index 0000000000..06edeb890c --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/DetailsSection/PayoutLimitValue.tsx @@ -0,0 +1,70 @@ +import { Trans } from '@lingui/macro' +import TooltipIcon from 'components/TooltipIcon' +import { CurrencyName } from 'constants/currency' +import { NativeTokenValue } from 'juice-sdk-react' +import { isInfinitePayoutLimit } from 'packages/v4/utils/fundingCycle' + +export function PayoutLimitValue({ + payoutLimit, + currencyName, + shortName, +}: { + payoutLimit: bigint | undefined + currencyName: CurrencyName | undefined + shortName?: boolean +}) { + const distributionLimitIsInfinite = + !payoutLimit || isInfinitePayoutLimit(payoutLimit) + const distributionLimitIsZero = payoutLimit === 0n + // const distributionLimitCurrency = currencyName + // ? getV4CurrencyOption(currencyName) + // : undefined + + const _tooltip = ( + + All of this project's ETH will be paid out. Token holders will + receive no ETH when redeeming their tokens. + + ) : distributionLimitIsZero ? ( + + No ETH can be paid out from the project. The ETH can only be + accessed by token holders that redeem their tokens. + + ) : ( + + If you don't raise this amount, your payout recipients will receive + their percentage of the available ETH. + + ) + } + placement={'topLeft'} + iconClassName="ml-1" + /> + ) + + const _text = distributionLimitIsInfinite ? ( + <> + {shortName ? ( + No limit + ) : ( + No limit (all available ETH) + )} + + ) : distributionLimitIsZero ? ( + <>{shortName ? Zero : Zero (no payouts)} + ) : ( + + ) + + return ( + + {_text} + {_tooltip} + + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/Tokens/IssuanceRateValue.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/Tokens/IssuanceRateValue.tsx new file mode 100644 index 0000000000..ac262b2222 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/FormattedRulesetValues/Tokens/IssuanceRateValue.tsx @@ -0,0 +1,22 @@ +import { Trans } from '@lingui/macro' +import { WEIGHT_UNCHANGED } from 'packages/v4/utils/fundingCycle' + +export function IssuanceRateValue({ + value, + tokenSymbol, + zeroAsUnchanged, +}: { + value: number + tokenSymbol: string + zeroAsUnchanged?: boolean +}) { + if (zeroAsUnchanged && value === WEIGHT_UNCHANGED) { + return Unchanged + } + + return ( + + {value} {tokenSymbol}/ETH + + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/PayoutsSectionDiff.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/PayoutsSectionDiff.tsx new file mode 100644 index 0000000000..f7b9c16d22 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/PayoutsSectionDiff.tsx @@ -0,0 +1,97 @@ +import { Trans, t } from '@lingui/macro' +import { FundingCycleListItem } from 'components/FundingCycleListItem' +import { getV4CurrencyOption } from 'packages/v4/utils/currency' +import { emptySectionClasses } from './DetailsSectionDiff' +import DiffedSplitList from './DiffedSplits/DiffedSplitList' +import { DiffSection } from './DiffSection' +import { PayoutLimitValue } from './FormattedRulesetValues/DetailsSection/PayoutLimitValue' +import { usePayoutsSectionValues } from './hooks/usePayoutsSectionValues' + +export function PayoutsSectionDiff() { + const { + sectionHasDiff, + advancedOptionsHasDiff, + + newCurrency, + currentCurrency, + + currentDistributionLimit, + newDistributionLimit, + distributionLimitHasDiff, + + currentPayoutSplits, + newPayoutSplits, + payoutSplitsHasDiff, + + newHoldFees, + currentHoldFees, + } = usePayoutsSectionValues() + + if (!sectionHasDiff) { + return ( +
+ No edits were made to payouts for this cycle. +
+ ) + } + + const roundingPrecision = newCurrency === 'ETH' ? 4 : 2 + + return ( + + {distributionLimitHasDiff && ( + + } + oldValue={ + + } + /> + )} + {payoutSplitsHasDiff && ( +
+
+ Payout recipients: +
+ +
+ )} + + } + advancedOptions={ + advancedOptionsHasDiff && ( + {newHoldFees.toString()}} + oldValue={ + {currentHoldFees.toString()} + } + /> + ) + } + /> + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx new file mode 100644 index 0000000000..69f82e0ece --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx @@ -0,0 +1,146 @@ +import { Trans, t } from '@lingui/macro' +import { Form } from 'antd' +import { useWatch } from 'antd/lib/form/Form' +import { CreateCollapse } from 'components/Create/components/CreateCollapse/CreateCollapse' +import { JuiceTextArea } from 'components/inputs/JuiceTextArea' +import TransactionModal from 'components/modals/TransactionModal' +import { useState } from 'react' +// import { useReconfigureFundingCycle } from '../../../hooks/useReconfigureFundingCycle' +import { useEditCycleFormContext } from '../EditCycleFormContext' +// import { usePrepareSaveEditCycleData } from '../hooks/usePrepareSaveEditCycleData' +import { TransactionSuccessModal } from '../TransactionSuccessModal' +import { DetailsSectionDiff } from './DetailsSectionDiff' +import { PayoutsSectionDiff } from './PayoutsSectionDiff' +import { SectionCollapseHeader } from './SectionCollapseHeader' +import { TokensSectionDiff } from './TokensSectionDiff' +import { useDetailsSectionValues } from './hooks/useDetailsSectionValues' +import { usePayoutsSectionValues } from './hooks/usePayoutsSectionValues' +import { useTokensSectionValues } from './hooks/useTokensSectionValues' + +export function ReviewConfirmModal({ + open, + onClose, +}: { + open: boolean + onClose: VoidFunction +}) { + const [editCycleSuccessModalOpen, setEditCycleSuccessModalOpen] = + useState(false) + + const { editCycleForm } = useEditCycleFormContext() + + const { sectionHasDiff: detailsSectionHasDiff } = useDetailsSectionValues() + const { sectionHasDiff: payoutsSectionHasDiff } = usePayoutsSectionValues() + const { sectionHasDiff: tokensSectionHasDiff } = useTokensSectionValues() + + const formHasChanges = + detailsSectionHasDiff || payoutsSectionHasDiff || tokensSectionHasDiff + + const memo = useWatch('memo', editCycleForm) + // const { editingFundingCycleConfig } = usePrepareSaveEditCycleData() + + // const { reconfigureLoading, reconfigureFundingCycle } = + // useReconfigureFundingCycle({ + // editingFundingCycleConfig, + // memo: memo ?? '', + // onComplete: () => { + // editCycleForm?.resetFields() + // setEditCycleSuccessModalOpen(true) + // onClose() + // }, + // }) + + const panelProps = { className: 'text-lg' } + + return ( + <> + Review & confirm} + destroyOnClose + onOk={() => null}//reconfigureFundingCycle()} + okText={Deploy changes} + okButtonProps={{ disabled: !formHasChanges }} + cancelButtonProps={{ hidden: true }} + onCancel={onClose} + confirmLoading={false}//reconfigureLoading} + > +

+ + Please check your changes carefully. Each deploy will incur a gas + fee. + +

+ + Cycle details} + hasDiff={detailsSectionHasDiff} + /> + } + {...panelProps} + > + + + Payouts} + hasDiff={payoutsSectionHasDiff} + /> + } + {...panelProps} + > + + + Tokens} + hasDiff={tokensSectionHasDiff} + /> + } + {...panelProps} + > + + + +
+ + Memo (optional) + +
+ + + +
+ setEditCycleSuccessModalOpen(false)} + content={ + <> +
+ Your updated cycle has been deployed +
+
+ + Changes will take effect in your next cycle as long as it starts + after your edit deadline. + +
+ + } + /> + + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/SectionCollapseHeader.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/SectionCollapseHeader.tsx new file mode 100644 index 0000000000..c88ffb4612 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/SectionCollapseHeader.tsx @@ -0,0 +1,28 @@ +import { Trans } from '@lingui/macro' +import { Badge } from 'components/Badge' + +export function SectionCollapseHeader({ + title, + hasDiff, +}: { + title: React.ReactNode + hasDiff: boolean +}) { + const sectionHasEditsCard = ( + + Edited + + ) + + const sectionNoEditsCard = ( + + No changes + + ) + return ( +
+ {title} + {hasDiff ? sectionHasEditsCard : sectionNoEditsCard} +
+ ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx new file mode 100644 index 0000000000..56a3c83d22 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/TokensSectionDiff.tsx @@ -0,0 +1,144 @@ +import { Trans, t } from '@lingui/macro' +import { FundingCycleListItem } from 'components/FundingCycleListItem' +import { useJBUpcomingRuleset } from 'packages/v4/hooks/useJBUpcomingRuleset' +import { emptySectionClasses } from './DetailsSectionDiff' +import DiffedSplitList from './DiffedSplits/DiffedSplitList' +import { DiffSection } from './DiffSection' +import { AllowedValue } from './FormattedRulesetValues/AllowedValue' +import { IssuanceRateValue } from './FormattedRulesetValues/Tokens/IssuanceRateValue' +import { useTokensSectionValues } from './hooks/useTokensSectionValues' + +export function TokensSectionDiff() { + const { + sectionHasDiff, + + newMintRate, + mintRateHasDiff, + + newReservedRate, + currentReservedRate, + reservedRateHasDiff, + + newReservedSplits, + currentReservedSplits, + reservedSplitsHasDiff, + + newDiscountRate, + currentDiscountRate, + discountRateHasDiff, + + newRedemptionRate, + currentRedemptionRate, + redemptionHasDiff, + + newAllowMinting, + currentAllowMinting, + allowMintingHasDiff, + + newTokenTransfers, + currentTokenTransfers, + tokenTransfersHasDiff, + + // unsafeFundingCycleProperties, + tokenSymbolPlural, + } = useTokensSectionValues() + + const { + ruleset: upcomingRuleset, + } = useJBUpcomingRuleset() + + if (!sectionHasDiff) { + return ( +
+ No edits were made to tokens for this cycle. +
+ ) + } + + const currentMintRateAfterDiscountRateApplied = upcomingRuleset?.weight.toFloat() + + return ( + + {mintRateHasDiff && currentMintRateAfterDiscountRateApplied && ( + + } + oldValue={ + + } + /> + )} + {discountRateHasDiff && currentDiscountRate && ( + + )} + {redemptionHasDiff && currentRedemptionRate && ( + + )} + {allowMintingHasDiff && ( + + } + oldValue={ + + } + /> + )} + {tokenTransfersHasDiff && ( + + } + oldValue={ + + } + /> + )} + {reservedRateHasDiff && currentReservedRate && ( + {newReservedRate}% + } + oldValue={{currentReservedRate}%} + /> + )} + {reservedSplitsHasDiff && ( +
+
+ Reserved recipients: +
+ +
+ )} + + } + /> + ) +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/useDetailsSectionValues.ts b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/useDetailsSectionValues.ts new file mode 100644 index 0000000000..e2d7005d31 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/useDetailsSectionValues.ts @@ -0,0 +1,95 @@ +import { getApprovalStrategyByAddress } from 'packages/v4/utils/approvalHooks' +import { otherUnitToSeconds } from 'utils/format/formatTime' +import { useEditCycleFormContext } from '../../EditCycleFormContext' + +export const useDetailsSectionValues = () => { + + const { editCycleForm, initialFormData } = useEditCycleFormContext() + + const currentDuration = otherUnitToSeconds({ + duration: initialFormData?.duration ?? 0, + unit: initialFormData?.durationUnit.value, + }) + + const newDuration = otherUnitToSeconds({ + duration: editCycleForm?.getFieldValue('duration'), + unit: editCycleForm?.getFieldValue('durationUnit')?.value, + }) + + const durationHasDiff = newDuration !== currentDuration + + const newBallot = getApprovalStrategyByAddress( + editCycleForm?.getFieldValue('approvalHook'), + ) + const currentBallot = initialFormData + ? getApprovalStrategyByAddress(initialFormData.approvalHook) + : undefined + const ballotHasDiff = newBallot?.address !== currentBallot?.address + + const newPausePay = Boolean(editCycleForm?.getFieldValue('pausePay')) + const currentPausePay = Boolean(initialFormData?.pausePay) + const pausePayHasDiff = currentPausePay !== newPausePay + + const newSetTerminals = Boolean( + editCycleForm?.getFieldValue('allowSetTerminals'), + ) + const currentSetTerminals = Boolean( + initialFormData?.allowSetTerminals, + ) + const allowSetTerminalsHasDiff = currentSetTerminals !== newSetTerminals + + const newAllowTerminalMigration = Boolean( + editCycleForm?.getFieldValue('allowTerminalMigration'), + ) + const currentAllowTerminalMigration = Boolean( + initialFormData?.allowTerminalMigration, + ) + const allowTerminalMigrationHasDiff = + currentAllowTerminalMigration !== newAllowTerminalMigration + + const newSetController = Boolean( + editCycleForm?.getFieldValue('allowSetController'), + ) + const currentSetController = Boolean( + initialFormData?.allowSetController, + ) + const allowSetControllerHasDiff = currentSetController !== newSetController + + const advancedOptionsHasDiff = + pausePayHasDiff || + allowSetTerminalsHasDiff || + allowSetControllerHasDiff || + allowTerminalMigrationHasDiff + + const sectionHasDiff = + durationHasDiff || ballotHasDiff || advancedOptionsHasDiff + + return { + currentDuration, + newDuration, + durationHasDiff, + + currentBallot, + newBallot, + ballotHasDiff, + + newPausePay, + currentPausePay, + pausePayHasDiff, + + newSetTerminals, + currentSetTerminals, + allowSetTerminalsHasDiff, + + newAllowTerminalMigration, + currentAllowTerminalMigration, + allowTerminalMigrationHasDiff, + + newSetController, + currentSetController, + allowSetControllerHasDiff, + + advancedOptionsHasDiff, + sectionHasDiff, + } +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/usePayoutsSectionValues.ts b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/usePayoutsSectionValues.ts new file mode 100644 index 0000000000..d3179df115 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/usePayoutsSectionValues.ts @@ -0,0 +1,70 @@ +import { WeiPerEther } from '@ethersproject/constants' +import { CurrencyName } from 'constants/currency' +import { JBSplit } from 'juice-sdk-core' +import { distributionLimitsEqual } from 'packages/v4/utils/distributions' +import { MAX_PAYOUT_LIMIT } from 'packages/v4/utils/math' +import { splitsListsHaveDiff } from 'packages/v4/utils/v4Splits' +import { useEditCycleFormContext } from '../../EditCycleFormContext' + +export const usePayoutsSectionValues = () => { + + const { editCycleForm, initialFormData } = useEditCycleFormContext() + + const newPayoutSplits: JBSplit[] = editCycleForm?.getFieldValue('payoutSplits') ?? [] + const currentPayoutSplits = initialFormData?.payoutSplits ?? [] + const payoutSplitsHasDiff = splitsListsHaveDiff( + currentPayoutSplits, + newPayoutSplits, + ) + + const newCurrency: CurrencyName = editCycleForm?.getFieldValue( + 'distributionLimitCurrency', + ) ?? 'ETH' + const currentCurrency = + initialFormData?.payoutLimitCurrency ?? + 'ETH' + const currencyHasDiff = currentCurrency !== newCurrency + + const newDistributionLimitNum: number = editCycleForm?.getFieldValue('payoutLimit') + const newDistributionLimit = + newDistributionLimitNum ? BigInt(newDistributionLimitNum) * WeiPerEther.toBigInt() : MAX_PAYOUT_LIMIT + + const currentDistributionLimitNum = initialFormData?.payoutLimit + const currentDistributionLimit = currentDistributionLimitNum ? BigInt(currentDistributionLimitNum) * WeiPerEther.toBigInt() : MAX_PAYOUT_LIMIT + + const distributionLimitHasDiff = + !distributionLimitsEqual(currentDistributionLimit, newDistributionLimit) || + currencyHasDiff + // TODO: When no limit is set and doesnt change, distributionLimitHasDiff still true + const distributionLimitIsInfinite = + !newDistributionLimit || newDistributionLimit === MAX_PAYOUT_LIMIT + + const newHoldFees = Boolean(editCycleForm?.getFieldValue('holdFees')) + const currentHoldFees = Boolean(initialFormData?.holdFees) + const holdFeesHasDiff = newHoldFees !== currentHoldFees + + const advancedOptionsHasDiff = holdFeesHasDiff + const sectionHasDiff = + distributionLimitHasDiff || payoutSplitsHasDiff || advancedOptionsHasDiff + + return { + newCurrency, + currentCurrency, + + currentDistributionLimit, + newDistributionLimit, + distributionLimitHasDiff, + distributionLimitIsInfinite, + + currentPayoutSplits, + newPayoutSplits, + payoutSplitsHasDiff, + + newHoldFees, + currentHoldFees, + holdFeesHasDiff, + + advancedOptionsHasDiff, + sectionHasDiff, + } +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/useTokensSectionValues.ts b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/useTokensSectionValues.ts new file mode 100644 index 0000000000..4f3161da98 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/hooks/useTokensSectionValues.ts @@ -0,0 +1,122 @@ +import { useJBTokenContext } from 'juice-sdk-react' +import round from 'lodash/round' +import { useJBUpcomingRuleset } from 'packages/v4/hooks/useJBUpcomingRuleset' +import { splitsListsHaveDiff } from 'packages/v4/utils/v4Splits' +import { tokenSymbolText } from 'utils/tokenSymbolText' +import { useEditCycleFormContext } from '../../EditCycleFormContext' +import { EditCycleFormFields } from '../../EditCycleFormFields' + +export const useTokensSectionValues = () => { + const { editCycleForm, initialFormData } = useEditCycleFormContext() + + const formValues: EditCycleFormFields = editCycleForm?.getFieldsValue(true) + + const { + ruleset: upcomingRuleset, + } = useJBUpcomingRuleset() + + const { token } = useJBTokenContext() + const tokenSymbol = token?.data?.symbol + + const tokenSymbolPlural = tokenSymbolText({ + tokenSymbol, + capitalize: false, + plural: true, + }) + const newMintRate = formValues.issuanceRate + + const newReservedRate = formValues.reservedPercent + const newReservedSplits = formValues.reservedTokensSplits + const newDiscountRate = formValues.decayPercent + const newRedemptionRate = formValues.redemptionRate + const newAllowMinting = formValues.allowOwnerMinting + const newTokenTransfers = formValues.tokenTransfers + + const currentMintRate = initialFormData?.issuanceRate + const currentReservedRate = initialFormData?.reservedPercent + const currentReservedSplits = initialFormData?.reservedTokensSplits + const currentDiscountRate = initialFormData?.decayPercent + const currentRedemptionRate = initialFormData?.redemptionRate + const currentAllowMinting = Boolean(initialFormData?.allowOwnerMinting) + const currentTokenTransfers = Boolean( + initialFormData?.tokenTransfers, + ) + + const onlyDiscountRateApplied = + upcomingRuleset && + newMintRate && + round(upcomingRuleset?.weight.toFloat(), 4) === round(newMintRate, 4) + + const mintRateHasDiff = !onlyDiscountRateApplied + + const reservedRateHasDiff = Boolean( + currentReservedRate && + newReservedRate !== currentReservedRate, + ) + + const reservedSplitsHasDiff = splitsListsHaveDiff( + currentReservedSplits, + newReservedSplits, + ) + + const discountRateHasDiff = Boolean( + currentDiscountRate && + newDiscountRate !== currentDiscountRate, + ) + + const redemptionHasDiff = + currentRedemptionRate && + newRedemptionRate !== currentRedemptionRate + + const allowMintingHasDiff = Boolean(newAllowMinting !== currentAllowMinting) + + const tokenTransfersHasDiff = Boolean( + newTokenTransfers !== currentTokenTransfers, + ) + + const advancedOptionsHasDiff = + reservedRateHasDiff || + discountRateHasDiff || + redemptionHasDiff || + allowMintingHasDiff || + tokenTransfersHasDiff + + const sectionHasDiff = + mintRateHasDiff || reservedSplitsHasDiff || advancedOptionsHasDiff + + return { + newMintRate, + currentMintRate, + mintRateHasDiff, + + newReservedRate, + currentReservedRate, + reservedRateHasDiff, + + newReservedSplits, + currentReservedSplits, + reservedSplitsHasDiff, + + newDiscountRate, + currentDiscountRate, + discountRateHasDiff, + + newRedemptionRate, + currentRedemptionRate, + redemptionHasDiff, + + newAllowMinting, + currentAllowMinting, + allowMintingHasDiff, + + newTokenTransfers, + currentTokenTransfers, + tokenTransfersHasDiff, + + tokenSymbolPlural, + // unsafeFundingCycleProperties, + + advancedOptionsHasDiff, + sectionHasDiff, + } +} diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/index.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/index.tsx new file mode 100644 index 0000000000..17f5cfc87e --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/ReviewConfirmModal/index.tsx @@ -0,0 +1 @@ +export * from './ReviewConfirmModal' diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/TransactionSuccessModal.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/TransactionSuccessModal.tsx new file mode 100644 index 0000000000..e7e76302e8 --- /dev/null +++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/TransactionSuccessModal.tsx @@ -0,0 +1,57 @@ +import { CheckCircleIcon } from '@heroicons/react/24/outline' +import { Trans } from '@lingui/macro' +import { Button, Modal } from 'antd' +import { useJBContractContext } from 'juice-sdk-react' +import Link from 'next/link' +import { settingsPagePath, v4ProjectRoute } from 'packages/v4/utils/routes' +import { ReactNode } from 'react' +import { useChainId } from 'wagmi' + +export function TransactionSuccessModal({ + open, + onClose, + content, +}: { + open: boolean + onClose: VoidFunction + content: ReactNode +}) { + const { projectId: projectIdBigInt } = useJBContractContext() + const projectId = Number(projectIdBigInt) + const chainId = useChainId() + + const checkIconWithBackground = ( +
+
+ +
+
+ ) + + const buttonClasses = 'w-[185px] h-12' + return ( + +
+ {checkIconWithBackground} + {content} +
+ + + + + + +
+
+
+ ) +} diff --git a/src/utils/format/formatTime.ts b/src/utils/format/formatTime.ts index 5c76fa4ce6..95a75032f1 100644 --- a/src/utils/format/formatTime.ts +++ b/src/utils/format/formatTime.ts @@ -63,7 +63,7 @@ export const otherUnitToSeconds = ({ unit, }: { duration: number - unit: DurationUnitsOption + unit: DurationUnitsOption | undefined }) => { if (!duration) return 0 switch (unit) {