diff --git a/src/components/AccountSettingsDashboard/components/ProjectUnwatchCell.tsx b/src/components/AccountSettingsDashboard/components/ProjectUnwatchCell.tsx
index c3759da52f..be54400bf7 100644
--- a/src/components/AccountSettingsDashboard/components/ProjectUnwatchCell.tsx
+++ b/src/components/AccountSettingsDashboard/components/ProjectUnwatchCell.tsx
@@ -1,9 +1,9 @@
import { useProjectHandleText } from 'hooks/useProjectHandleText'
import { useProjectMetadata } from 'hooks/useProjectMetadata'
import Link from 'next/link'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useMemo } from 'react'
import { twMerge } from 'tailwind-merge'
-import { v2v3ProjectRoute } from 'utils/routes'
import { useProjectUnwatchCellData } from '../hooks/useProjectUnwatchCellData'
import { UnwatchButton } from './UnwatchButton'
diff --git a/src/components/Create/components/pages/ProjectDetails/ProjectDetailsPage.tsx b/src/components/Create/components/pages/ProjectDetails/ProjectDetailsPage.tsx
index ee3d20a30d..707b5b4ebe 100644
--- a/src/components/Create/components/pages/ProjectDetails/ProjectDetailsPage.tsx
+++ b/src/components/Create/components/pages/ProjectDetails/ProjectDetailsPage.tsx
@@ -9,7 +9,6 @@ import FormattedNumberInput from 'components/inputs/FormattedNumberInput'
import { FormImageUploader } from 'components/inputs/FormImageUploader'
import { JuiceTextArea } from 'components/inputs/JuiceTextArea'
import { JuiceInput } from 'components/inputs/JuiceTextInput'
-import PrefixedInput from 'components/inputs/PrefixedInput'
import { RichEditor } from 'components/RichEditor'
import { CREATE_FLOW } from 'constants/fathomEvents'
import { constants } from 'ethers'
@@ -133,7 +132,7 @@ export const ProjectDetailsPage: React.FC<
-
+
@@ -277,3 +276,39 @@ const AmountInput = ({
)
}
+
+// Exists just to solve an issue where a user might paste a twitter url instead of just the handle
+export const TwitterHandleInputWrapper = ({
+ value,
+ onChange,
+}: {
+ value?: string
+ onChange?: (val: string) => void
+}) => {
+ const [_value, _setValue] = useState(value ?? '')
+ const setValue = onChange ?? _setValue
+ value = value ?? _value
+
+ const onInputChange = useCallback(
+ (value: string | undefined) => {
+ const httpOrHttpsRegex = /^(http|https):\/\//
+ if (value?.length && value.match(httpOrHttpsRegex)) {
+ const handle = value.split('/').pop()
+ if (handle) {
+ setValue(handle)
+ return
+ }
+ }
+ setValue(value ?? '')
+ },
+ [setValue],
+ )
+
+ return (
+ onInputChange(e.target.value)}
+ prefix="@"
+ />
+ )
+}
diff --git a/src/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx b/src/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx
index c7132a21d9..f4a1e81c33 100644
--- a/src/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx
+++ b/src/components/Create/components/pages/ReviewDeploy/ReviewDeployPage.tsx
@@ -16,7 +16,7 @@ import { useDispatch } from 'react-redux'
import { useAppSelector } from 'redux/hooks/useAppSelector'
import { useSetCreateFurthestPageReached } from 'redux/hooks/useEditingCreateFurthestPageReached'
import { editingV2ProjectActions } from 'redux/slices/editingV2Project'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { CreateBadge } from '../../CreateBadge'
import { CreateCollapse } from '../../CreateCollapse/CreateCollapse'
import { Wizard } from '../../Wizard/Wizard'
diff --git a/src/components/Home/FaqList/QAs.tsx b/src/components/Home/FaqList/QAs.tsx
index 1651d32c2e..bf2680663f 100644
--- a/src/components/Home/FaqList/QAs.tsx
+++ b/src/components/Home/FaqList/QAs.tsx
@@ -2,7 +2,7 @@ import { Trans } from '@lingui/macro'
import ExternalLink from 'components/ExternalLink'
import Link from 'next/link'
import { ReactNode } from 'react'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
const JB_FEE = 2.5
diff --git a/src/components/Home/HomepageProjectCard.tsx b/src/components/Home/HomepageProjectCard.tsx
index 75d7e9ec23..641175d84d 100644
--- a/src/components/Home/HomepageProjectCard.tsx
+++ b/src/components/Home/HomepageProjectCard.tsx
@@ -6,7 +6,7 @@ import ETHAmount from 'components/currency/ETHAmount'
import { PV_V2 } from 'constants/pv'
import { useProjectMetadata } from 'hooks/useProjectMetadata'
import { SubgraphQueryProject } from 'models/subgraphProjects'
-import { v2v3ProjectRoute } from 'utils/routes'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
function Statistic({
name,
diff --git a/src/components/Home/JuicyPicksSection/SpotlightProjectCard.tsx b/src/components/Home/JuicyPicksSection/SpotlightProjectCard.tsx
index 5070664e54..84efcf289e 100644
--- a/src/components/Home/JuicyPicksSection/SpotlightProjectCard.tsx
+++ b/src/components/Home/JuicyPicksSection/SpotlightProjectCard.tsx
@@ -13,9 +13,9 @@ import { Project } from 'generated/graphql'
import { useProjectMetadata } from 'hooks/useProjectMetadata'
import { useProjectTrendingPercentageIncrease } from 'hooks/useProjectTrendingPercentageIncrease'
import Link from 'next/link'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { twJoin } from 'tailwind-merge'
import { ipfsUriToGatewayUrl } from 'utils/ipfs'
-import { v2v3ProjectRoute } from 'utils/routes'
function Statistic({
name,
diff --git a/src/components/PayoutsTable/SwitchToUnlimitedModal.tsx b/src/components/PayoutsTable/SwitchToUnlimitedModal.tsx
index c01782ee70..4d3af63535 100644
--- a/src/components/PayoutsTable/SwitchToUnlimitedModal.tsx
+++ b/src/components/PayoutsTable/SwitchToUnlimitedModal.tsx
@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { Modal } from 'antd'
import { ExternalLinkWithIcon } from 'components/ExternalLinkWithIcon'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
export function SwitchToUnlimitedModal({
open,
diff --git a/src/components/Project/ProjectHeader/EditProjectHandleButton.tsx b/src/components/Project/ProjectHeader/EditProjectHandleButton.tsx
index 1aab19889e..4cb86dd029 100644
--- a/src/components/Project/ProjectHeader/EditProjectHandleButton.tsx
+++ b/src/components/Project/ProjectHeader/EditProjectHandleButton.tsx
@@ -4,8 +4,8 @@ import { Button, Tooltip } from 'antd'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import Link from 'next/link'
import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
+import { settingsPagePath } from 'packages/v2v3/utils/routes'
import { useContext } from 'react'
-import { settingsPagePath } from 'utils/routes'
export function EditProjectHandleButton() {
const { projectId } = useContext(ProjectMetadataContext)
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/AdvancedDropdown.tsx b/src/components/Project/ProjectSettings/AdvancedDropdown.tsx
similarity index 100%
rename from src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/AdvancedDropdown.tsx
rename to src/components/Project/ProjectSettings/AdvancedDropdown.tsx
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/EditCycleFormSection.tsx b/src/components/Project/ProjectSettings/EditCycleFormSection.tsx
similarity index 100%
rename from src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/EditCycleFormSection.tsx
rename to src/components/Project/ProjectSettings/EditCycleFormSection.tsx
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/EditCycleHeader.tsx b/src/components/Project/ProjectSettings/EditCycleHeader.tsx
similarity index 100%
rename from src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/EditCycleHeader.tsx
rename to src/components/Project/ProjectSettings/EditCycleHeader.tsx
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectDetailsSettingsPage/ProjectDetailsForm.tsx b/src/components/Project/ProjectSettings/ProjectDetailsForm.tsx
similarity index 100%
rename from src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectDetailsSettingsPage/ProjectDetailsForm.tsx
rename to src/components/Project/ProjectSettings/ProjectDetailsForm.tsx
diff --git a/src/components/ProjectCard.tsx b/src/components/ProjectCard.tsx
index ba76b1d585..259bfda4d9 100644
--- a/src/components/ProjectCard.tsx
+++ b/src/components/ProjectCard.tsx
@@ -6,11 +6,11 @@ import { useProjectHandleText } from 'hooks/useProjectHandleText'
import Link from 'next/link'
import { isHardArchived } from 'utils/archived'
import { formatDate } from 'utils/format/formatDate'
-import { v2v3ProjectRoute } from 'utils/routes'
import { useProjectMetadata } from 'hooks/useProjectMetadata'
import { useSubtitle } from 'hooks/useSubtitle'
import { SubgraphQueryProject } from 'models/subgraphProjects'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { ArchivedBadge } from './ArchivedBadge'
import Loading from './Loading'
import ProjectLogo from './ProjectLogo'
diff --git a/src/components/Projects/TrendingProjectCard.tsx b/src/components/Projects/TrendingProjectCard.tsx
index a650d0c239..58e5624dbe 100644
--- a/src/components/Projects/TrendingProjectCard.tsx
+++ b/src/components/Projects/TrendingProjectCard.tsx
@@ -9,7 +9,7 @@ import { useProjectMetadata } from 'hooks/useProjectMetadata'
import { useProjectTrendingPercentageIncrease } from 'hooks/useProjectTrendingPercentageIncrease'
import { DBProject } from 'models/dbProject'
import Link from 'next/link'
-import { v2v3ProjectRoute } from 'utils/routes'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { TRENDING_WINDOW_DAYS } from './RankingExplanation'
export default function TrendingProjectCard({
diff --git a/src/components/QuickProjectSearch/QuickProjectSearchModal.tsx b/src/components/QuickProjectSearch/QuickProjectSearchModal.tsx
index 75edaa4504..fe0c702054 100644
--- a/src/components/QuickProjectSearch/QuickProjectSearchModal.tsx
+++ b/src/components/QuickProjectSearch/QuickProjectSearchModal.tsx
@@ -16,9 +16,9 @@ import { useDBProjectsQuery } from 'hooks/useDBProjects'
import { useRouter } from 'next/router'
import V1ProjectHandle from 'packages/v1/components/shared/V1ProjectHandle'
import V2V3ProjectHandleLink from 'packages/v2v3/components/shared/V2V3ProjectHandleLink'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import React, { useCallback, useContext, useEffect, useState } from 'react'
import { twMerge } from 'tailwind-merge'
-import { v2v3ProjectRoute } from 'utils/routes'
import { QuickProjectSearchContext } from './QuickProjectSearchContext'
const INPUT_ID = 'quickProjectSearch'
diff --git a/src/components/WalletContributionCard.tsx b/src/components/WalletContributionCard.tsx
index ae1b37c4d0..ab32fb69f6 100644
--- a/src/components/WalletContributionCard.tsx
+++ b/src/components/WalletContributionCard.tsx
@@ -4,9 +4,9 @@ import { PV_V2 } from 'constants/pv'
import { WalletContributionsQuery } from 'generated/graphql'
import { useProjectMetadata } from 'hooks/useProjectMetadata'
import Link from 'next/link'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { isHardArchived } from 'utils/archived'
import { formatDate } from 'utils/format/formatDate'
-import { v2v3ProjectRoute } from 'utils/routes'
import { ArchivedBadge } from './ArchivedBadge'
import ETHAmount from './currency/ETHAmount'
diff --git a/src/components/buttons/ErrorNotificationButtons.tsx b/src/components/buttons/ErrorNotificationButtons.tsx
index ee33f1a7fc..e4ffae3a33 100644
--- a/src/components/buttons/ErrorNotificationButtons.tsx
+++ b/src/components/buttons/ErrorNotificationButtons.tsx
@@ -2,7 +2,7 @@ import { WarningOutlined } from '@ant-design/icons'
import { Trans } from '@lingui/macro'
import { Button } from 'antd'
import { LanguageProvider } from 'contexts/Language/LanguageProvider'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import ExternalLink from '../ExternalLink'
const resetSite = () => {
diff --git a/src/components/formItems/ProjectTwitter.tsx b/src/components/formItems/ProjectTwitter.tsx
index 6a513859e2..5230ee6147 100644
--- a/src/components/formItems/ProjectTwitter.tsx
+++ b/src/components/formItems/ProjectTwitter.tsx
@@ -1,6 +1,7 @@
import { t } from '@lingui/macro'
-import { Form, Input } from 'antd'
+import { Form } from 'antd'
+import { TwitterHandleInputWrapper } from 'components/Create/components/pages/ProjectDetails/ProjectDetailsPage'
import { FormItemExt } from './formItemExt'
export default function ProjectTwitter({
@@ -14,12 +15,7 @@ export default function ProjectTwitter({
label={hideLabel ? undefined : t`Twitter handle`}
{...formItemProps}
>
-
+
)
}
diff --git a/src/components/inputs/ReconfigurationStrategy/CustomStrategyInput.tsx b/src/components/inputs/ReconfigurationStrategy/CustomStrategyInput.tsx
index 55b294d9b9..fbb8e13593 100644
--- a/src/components/inputs/ReconfigurationStrategy/CustomStrategyInput.tsx
+++ b/src/components/inputs/ReconfigurationStrategy/CustomStrategyInput.tsx
@@ -7,7 +7,7 @@ import { useWallet } from 'hooks/Wallet'
import { FormItemInput } from 'models/formItemInput'
import { NetworkName } from 'models/networkName'
import { MouseEventHandler } from 'react'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
export const CustomStrategyInput: React.FC<
React.PropsWithChildren<
diff --git a/src/components/modals/LegalNoticeModal.tsx b/src/components/modals/LegalNoticeModal.tsx
index 211b75e501..206cc28864 100644
--- a/src/components/modals/LegalNoticeModal.tsx
+++ b/src/components/modals/LegalNoticeModal.tsx
@@ -1,7 +1,7 @@
import { t } from '@lingui/macro'
import { Modal, ModalProps } from 'antd'
import ExternalLink from 'components/ExternalLink'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
export function LegalNoticeModal(props: ModalProps) {
return (
diff --git a/src/components/strings.tsx b/src/components/strings.tsx
index ba6a63167b..36d781c32f 100644
--- a/src/components/strings.tsx
+++ b/src/components/strings.tsx
@@ -44,7 +44,8 @@ export const LOCKED_PAYOUT_EXPLANATION = (
import ExternalLink from 'components/ExternalLink'
import Link from 'next/link'
-import { helpPagePath, v2v3ProjectRoute } from 'utils/routes'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
export const DISTRIBUTION_LIMIT_EXPLANATION = (
diff --git a/src/packages/v1/components/V1Project/V1ProjectHeader/ProjectHeader.tsx b/src/packages/v1/components/V1Project/V1ProjectHeader/ProjectHeader.tsx
index ab59b6d399..c872287a24 100644
--- a/src/packages/v1/components/V1Project/V1ProjectHeader/ProjectHeader.tsx
+++ b/src/packages/v1/components/V1Project/V1ProjectHeader/ProjectHeader.tsx
@@ -10,10 +10,10 @@ import { ProjectTagsList } from 'components/ProjectTags/ProjectTagsList'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import { useGnosisSafe } from 'hooks/safe/useGnosisSafe'
import { ContractVersionSelect } from 'packages/v2v3/components/V2V3Project/V2V3ProjectHeaderActions/ContractVersionSelect'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useContext } from 'react'
import { twMerge } from 'tailwind-merge'
import { ipfsUriToGatewayUrl } from 'utils/ipfs'
-import { v2v3ProjectRoute } from 'utils/routes'
import SocialLinks from './SocialLinks'
function ProjectSubheading({
diff --git a/src/packages/v1/components/shared/forms/BudgetForm.tsx b/src/packages/v1/components/shared/forms/BudgetForm.tsx
index d2f7bb1ee9..98ae72f9fe 100644
--- a/src/packages/v1/components/shared/forms/BudgetForm.tsx
+++ b/src/packages/v1/components/shared/forms/BudgetForm.tsx
@@ -19,7 +19,7 @@ import { useAppDispatch } from 'redux/hooks/useAppDispatch'
import { useEditingV1FundingCycleSelector } from 'redux/hooks/useAppSelector'
import { editingProjectActions } from 'redux/slices/editingProject'
import { fromWad } from 'utils/format/formatNumber'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import {
CYCLE_EXPLANATION,
diff --git a/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/PayRedeemCard/PayProjectModal/PayProjectModal.tsx b/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/PayRedeemCard/PayProjectModal/PayProjectModal.tsx
index 53e4a0124c..466aede953 100644
--- a/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/PayRedeemCard/PayProjectModal/PayProjectModal.tsx
+++ b/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/PayRedeemCard/PayProjectModal/PayProjectModal.tsx
@@ -5,7 +5,7 @@ import { JuiceModal } from 'components/modals/JuiceModal'
import { Formik } from 'formik'
import Image from "next/legacy/image"
import { twMerge } from 'tailwind-merge'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { MessageSection } from './components/MessageSection'
import { ReceiveSection } from './components/ReceiveSection'
import {
diff --git a/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/SuccessPayView/SuccessPayView.tsx b/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/SuccessPayView/SuccessPayView.tsx
index 4d3bd5a3eb..6d99be376b 100644
--- a/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/SuccessPayView/SuccessPayView.tsx
+++ b/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/SuccessPayView/SuccessPayView.tsx
@@ -4,7 +4,7 @@ import { Button } from 'antd'
import { SubscribeButton } from 'components/buttons/SubscribeButton/SubscribeButton'
import dynamic from 'next/dynamic'
import Link from 'next/link'
-import { v2v3ProjectRoute } from 'utils/routes'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { SuccessNftItem } from './components/SuccessNftItem'
import { SuccessPayCard } from './components/SuccessPayCard'
import { SuccessTokensItem } from './components/SuccessTokensItem'
diff --git a/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/V2V3ProjectHeader.tsx b/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/V2V3ProjectHeader.tsx
index 6cd2a4d221..dca6c7b878 100644
--- a/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/V2V3ProjectHeader.tsx
+++ b/src/packages/v2v3/components/V2V3Project/ProjectDashboard/components/V2V3ProjectHeader.tsx
@@ -18,8 +18,8 @@ import { useV2V3ProjectHeader } from 'packages/v2v3/components/V2V3Project/Proje
import V2V3ProjectHandleLink from 'packages/v2v3/components/shared/V2V3ProjectHandleLink'
import { useV2V3WalletHasPermission } from 'packages/v2v3/hooks/contractReader/useV2V3WalletHasPermission'
import { V2V3OperatorPermission } from 'packages/v2v3/models/v2v3Permissions'
+import { settingsPagePath, v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { twMerge } from 'tailwind-merge'
-import { settingsPagePath, v2v3ProjectRoute } from 'utils/routes'
import { SocialLink } from '../hooks/useAboutPanel'
export const V2V3ProjectHeader = ({ className }: { className?: string }) => {
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 c3129c05d7..fa2ee206eb 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/DataSourceListItems/index.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3FundingCycleSection/FundingCycleDetails/DataSourceListItems/index.tsx
@@ -15,7 +15,7 @@ import { JB721DelegateContractsContext } from 'packages/v2v3/contexts/NftRewards
import { V2V3FundingCycleMetadata } from 'packages/v2v3/models/fundingCycle'
import { useContext } from 'react'
import { formatBoolean } from 'utils/format/formatBoolean'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { FundingCycleListItem } from '../FundingCycleListItem'
function DataSourceAddressValue({ address }: { address: string | undefined }) {
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectContractsDashboard/V2V3ProjectContractsDashboard.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectContractsDashboard/V2V3ProjectContractsDashboard.tsx
index e44681ec0c..bab80c47d7 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectContractsDashboard/V2V3ProjectContractsDashboard.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectContractsDashboard/V2V3ProjectContractsDashboard.tsx
@@ -15,8 +15,8 @@ import {
SUPPORTED_PAYMENT_TERMINALS,
V2V3ContractName,
} from 'packages/v2v3/models/contracts'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useContext } from 'react'
-import { v2v3ProjectRoute } from 'utils/routes'
import { V2V3ProjectHeaderActions } from '../V2V3ProjectHeaderActions/V2V3ProjectHeaderActions'
/**
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectHeaderActions/V2V3ProjectHeaderActions.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectHeaderActions/V2V3ProjectHeaderActions.tsx
index f7f20216fd..24d5312268 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectHeaderActions/V2V3ProjectHeaderActions.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectHeaderActions/V2V3ProjectHeaderActions.tsx
@@ -11,8 +11,8 @@ import { V2V3ProjectToolsDrawer } from 'packages/v2v3/components/V2V3Project/V2V
import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
import { useV2V3WalletHasPermission } from 'packages/v2v3/hooks/contractReader/useV2V3WalletHasPermission'
import { V2V3OperatorPermission } from 'packages/v2v3/models/v2v3Permissions'
+import { settingsPagePath } from 'packages/v2v3/utils/routes'
import { useContext, useState } from 'react'
-import { settingsPagePath } from 'utils/routes'
export function V2V3ProjectHeaderActions() {
const wallet = useWallet()
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/ProjectSettingsLayout.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/ProjectSettingsLayout.tsx
index bf6994a242..4fb78b79f7 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/ProjectSettingsLayout.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/ProjectSettingsLayout.tsx
@@ -3,8 +3,8 @@ import { Trans } from '@lingui/macro'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import Link from 'next/link'
import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useContext } from 'react'
-import { v2v3ProjectRoute } from 'utils/routes'
export const ProjectSettingsLayout: React.FC = ({
children,
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/TransactionSuccessModal.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/TransactionSuccessModal.tsx
index 8c0b06e5aa..2e4e819e0a 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/TransactionSuccessModal.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/TransactionSuccessModal.tsx
@@ -4,8 +4,8 @@ import { Button, Modal } from 'antd'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import Link from 'next/link'
import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
+import { settingsPagePath, v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { ReactNode, useContext } from 'react'
-import { settingsPagePath, v2v3ProjectRoute } from 'utils/routes'
export function TransactionSuccessModal({
open,
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useSettingsPagePath.ts b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useSettingsPagePath.ts
index 86a62f5de9..75ce7c13aa 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useSettingsPagePath.ts
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/hooks/useSettingsPagePath.ts
@@ -1,7 +1,7 @@
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
+import { settingsPagePath } from 'packages/v2v3/utils/routes'
import { useContext } from 'react'
-import { settingsPagePath } from 'utils/routes'
import { V2V3SettingsPageKey } from '../ProjectSettingsDashboard'
export function useSettingsPagePath(key?: V2V3SettingsPageKey) {
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/DetailsSection/DetailsSection.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/DetailsSection/DetailsSection.tsx
index 4941fec10b..afe2125b65 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/DetailsSection/DetailsSection.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/DetailsSection/DetailsSection.tsx
@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro'
+import { EditCycleHeader } from 'components/Project/ProjectSettings/EditCycleHeader'
import { CYCLE_EXPLANATION } from 'components/strings'
-import { EditCycleHeader } from '../EditCycleHeader'
import CycleDeadlineDropdown from './CycleDeadlineDropdown'
import { DetailsSectionAdvanced } from './DetailsSectionAdvanced'
import { DurationFields } from './DurationFields'
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/DetailsSection/DetailsSectionAdvanced.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/DetailsSection/DetailsSectionAdvanced.tsx
index 51be8a0aa3..c3a865bc23 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/DetailsSection/DetailsSectionAdvanced.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/DetailsSection/DetailsSectionAdvanced.tsx
@@ -1,5 +1,6 @@
import { Trans } from '@lingui/macro'
import { Form } from 'antd'
+import { AdvancedDropdown } from 'components/Project/ProjectSettings/AdvancedDropdown'
import TooltipLabel from 'components/TooltipLabel'
import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
import {
@@ -8,7 +9,6 @@ import {
TERMINAL_CONFIG_EXPLANATION,
TERMINAL_MIGRATION_EXPLANATION,
} from 'components/strings'
-import { AdvancedDropdown } from '../AdvancedDropdown'
export function DetailsSectionAdvanced() {
return (
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/EditCyclePage.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/EditCyclePage.tsx
index 3b4e67276e..74075e44c3 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/EditCyclePage.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/EditCyclePage.tsx
@@ -2,15 +2,16 @@ import { Trans } from '@lingui/macro'
import { Button, Form, Tooltip } from 'antd'
import { ExternalLinkWithIcon } from 'components/ExternalLinkWithIcon'
import Loading from 'components/Loading'
+import EditCycleFormSection from 'components/Project/ProjectSettings/EditCycleFormSection'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
+import { settingsPagePath } from 'packages/v2v3/utils/routes'
import { useContext, useEffect, useRef, useState } from 'react'
-import { helpPagePath, settingsPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { DetailsSection } from './DetailsSection'
import { useEditCycleFormContext } from './EditCycleFormContext'
-import EditCycleFormSection from './EditCycleFormSection'
import { PayoutsSection } from './PayoutsSection/PayoutsSection'
import { ReviewConfirmModal } from './ReviewConfirmModal'
import { TokensSection } from './TokensSection'
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/PayoutsSection/PayoutsSection.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/PayoutsSection/PayoutsSection.tsx
index 2815f10631..ebac1f6221 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/PayoutsSection/PayoutsSection.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/PayoutsSection/PayoutsSection.tsx
@@ -2,10 +2,10 @@ import { Trans } from '@lingui/macro'
import { Form } from 'antd'
import { useWatch } from 'antd/lib/form/Form'
import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
+import { AdvancedDropdown } from 'components/Project/ProjectSettings/AdvancedDropdown'
import { CurrencyName } from 'constants/currency'
import { PayoutsTable } from 'packages/v2v3/components/shared/PayoutsTable/PayoutsTable'
import { Split } from 'packages/v2v3/models/splits'
-import { AdvancedDropdown } from '../AdvancedDropdown'
import { useEditCycleFormContext } from '../EditCycleFormContext'
export function PayoutsSection() {
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/IssuanceRateReductionField.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/IssuanceRateReductionField.tsx
index 9110cfd87b..b7347298ab 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/IssuanceRateReductionField.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/IssuanceRateReductionField.tsx
@@ -3,7 +3,7 @@ import { ExternalLinkWithIcon } from 'components/ExternalLinkWithIcon'
import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
import NumberSlider from 'components/inputs/NumberSlider'
import { useState } from 'react'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { useEditCycleFormContext } from '../EditCycleFormContext'
import { zeroPercentDisabledNoticed } from './RedemptionRateField'
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/RedemptionRateField.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/RedemptionRateField.tsx
index 4fbd5367e2..d0fbb6f64c 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/RedemptionRateField.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/RedemptionRateField.tsx
@@ -5,7 +5,7 @@ import { TokenRedemptionRateGraph } from 'components/TokenRedemptionRateGraph/To
import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
import NumberSlider from 'components/inputs/NumberSlider'
import { useState } from 'react'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { useEditCycleFormContext } from '../EditCycleFormContext'
export const zeroPercentDisabledNoticed = (
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/ReservedTokensField.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/ReservedTokensField.tsx
index a430826b70..a8d0312a8a 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/ReservedTokensField.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/ReservedTokensField.tsx
@@ -4,13 +4,13 @@ import { ExternalLinkWithIcon } from 'components/ExternalLinkWithIcon'
import { FormItems } from 'components/formItems'
import { ItemNoInput } from 'components/formItems/ItemNoInput'
import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
+import { AdvancedDropdown } from 'components/Project/ProjectSettings/AdvancedDropdown'
import { Split } from 'packages/v2v3/models/splits'
import { SPLITS_TOTAL_PERCENT } from 'packages/v2v3/utils/math'
import { totalSplitsPercent } from 'packages/v2v3/utils/v2v3Splits'
import { useState } from 'react'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { V2V3EditReservedTokens } from '../../ReservedTokensSettingsPage/V2V3EditReservedTokens'
-import { AdvancedDropdown } from '../AdvancedDropdown'
import { useEditCycleFormContext } from '../EditCycleFormContext'
import { zeroPercentDisabledNoticed } from './RedemptionRateField'
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/TokensSection.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/TokensSection.tsx
index ee6f2329d6..4e961c6e62 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/TokensSection.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/TokensSection.tsx
@@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
-import { EditCycleHeader } from '../EditCycleHeader'
+import { EditCycleHeader } from 'components/Project/ProjectSettings/EditCycleHeader'
import { MintRateField } from './MintRateField'
import { TokensSectionAdvanced } from './TokensSectionAdvanced'
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/TokensSectionAdvanced.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/TokensSectionAdvanced.tsx
index 80afa59cae..742ed459f1 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/TokensSectionAdvanced.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/EditCyclePage/TokensSection/TokensSectionAdvanced.tsx
@@ -1,7 +1,7 @@
import { Trans } from '@lingui/macro'
import { Form } from 'antd'
import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
-import { AdvancedDropdown } from '../AdvancedDropdown'
+import { AdvancedDropdown } from 'components/Project/ProjectSettings/AdvancedDropdown'
import { IssuanceRateReductionField } from './IssuanceRateReductionField'
import { RedemptionRateField } from './RedemptionRateField'
import { ReservedTokensField } from './ReservedTokensField'
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/PayoutsSettingsPage/V2V3EditPayouts.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/PayoutsSettingsPage/V2V3EditPayouts.tsx
index d7d2c8b16f..0e9740d223 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/PayoutsSettingsPage/V2V3EditPayouts.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/PayoutsSettingsPage/V2V3EditPayouts.tsx
@@ -19,11 +19,11 @@ import { PayoutCard } from 'packages/v2v3/components/shared/PayoutCard/PayoutCar
import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption'
import { parseV2SplitsCsv } from 'packages/v2v3/utils/csv'
import { isInfiniteDistributionLimit } from 'packages/v2v3/utils/fundingCycle'
+import { settingsPagePath } from 'packages/v2v3/utils/routes'
import { allocationToSplit, splitToAllocation } from 'packages/v2v3/utils/splitToAllocation'
import { twMerge } from 'tailwind-merge'
import { formatFundingTarget } from 'utils/format/formatFundingTarget'
import { formatPercent } from 'utils/format/formatPercent'
-import { settingsPagePath } from 'utils/routes'
export const V2V3EditPayouts = ({
editingSplits,
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProcessHeldFeesPage/ProcessHeldFeesPage.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProcessHeldFeesPage/ProcessHeldFeesPage.tsx
index ba62b3b41b..595c836628 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProcessHeldFeesPage/ProcessHeldFeesPage.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProcessHeldFeesPage/ProcessHeldFeesPage.tsx
@@ -7,9 +7,10 @@ import { useHeldFeesOf } from 'packages/v2v3/hooks/contractReader/useHeldFeesOf'
import { useV2V3WalletHasPermission } from 'packages/v2v3/hooks/contractReader/useV2V3WalletHasPermission'
import { useProcessHeldFeesTx } from 'packages/v2v3/hooks/transactor/useProcessHeldFeesTx'
import { V2V3OperatorPermission } from 'packages/v2v3/models/v2v3Permissions'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useState } from 'react'
+import { helpPagePath } from 'utils/helpPagePath'
import { emitErrorNotification } from 'utils/notifications'
-import { helpPagePath, v2v3ProjectRoute } from 'utils/routes'
export function ProcessHeldFeesPage() {
const [processingHeldFees, setProcessingHeldFees] = useState()
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectDetailsSettingsPage/ProjectDetailsSettingsPage.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectDetailsSettingsPage/ProjectDetailsSettingsPage.tsx
index e7536cdaad..d0c8527a2a 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectDetailsSettingsPage/ProjectDetailsSettingsPage.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectDetailsSettingsPage/ProjectDetailsSettingsPage.tsx
@@ -1,13 +1,11 @@
import { useForm } from 'antd/lib/form/Form'
+import { ProjectDetailsForm, ProjectDetailsFormFields } from 'components/Project/ProjectSettings/ProjectDetailsForm'
import { PROJECT_PAY_CHARACTER_LIMIT } from 'constants/numbers'
import { PV_V2 } from 'constants/pv'
import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
import { uploadProjectMetadata } from 'lib/api/ipfs'
import { revalidateProject } from 'lib/api/nextjs'
-import {
- ProjectDetailsForm,
- ProjectDetailsFormFields,
-} from 'packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectDetailsSettingsPage/ProjectDetailsForm'
+
import { useEditProjectDetailsTx } from 'packages/v2v3/hooks/transactor/useEditProjectDetailsTx'
import { useCallback, useContext, useEffect, useState } from 'react'
import { withoutHttps } from 'utils/http'
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectHandleSettingsPage.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectHandleSettingsPage.tsx
index 4640da9e83..cd2921db8d 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectHandleSettingsPage.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectSettings/pages/ProjectHandleSettingsPage.tsx
@@ -14,8 +14,8 @@ import useProjectENSName from 'packages/v2v3/hooks/contractReader/useProjectENSN
import { useProjectHandleENSTextRecord } from 'packages/v2v3/hooks/contractReader/useProjectHandleENSTextRecord'
import { useEditV2V3ProjectHandleTx } from 'packages/v2v3/hooks/transactor/useEditV2V3ProjectHandleTx'
import { useSetENSTextRecordForHandleTx } from 'packages/v2v3/hooks/transactor/useSetENSTextRecordForHandleTx'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useCallback, useContext, useEffect, useState } from 'react'
-import { v2v3ProjectRoute } from 'utils/routes'
export function ProjectHandleSettingsPage() {
const { handle } = useContext(V2V3ProjectContext)
diff --git a/src/packages/v2v3/components/V2V3Project/V2V3ProjectToolsDrawer/V2V3ProjectToolsDrawer.tsx b/src/packages/v2v3/components/V2V3Project/V2V3ProjectToolsDrawer/V2V3ProjectToolsDrawer.tsx
index 843dce8a41..8cff805718 100644
--- a/src/packages/v2v3/components/V2V3Project/V2V3ProjectToolsDrawer/V2V3ProjectToolsDrawer.tsx
+++ b/src/packages/v2v3/components/V2V3Project/V2V3ProjectToolsDrawer/V2V3ProjectToolsDrawer.tsx
@@ -12,12 +12,9 @@ import Link from 'next/link'
import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
import { useAddToBalanceTx } from 'packages/v2v3/hooks/transactor/AddToBalanceTx'
import { useDeployProjectPayerTx } from 'packages/v2v3/hooks/transactor/useDeployProjectPayerTx'
-import {
- ETHPayoutSplitGroup,
- ReservedTokensSplitGroup,
-} from 'packages/v2v3/models/splits'
+import { ETHPayoutSplitGroup, ReservedTokensSplitGroup } from 'packages/v2v3/models/splits'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useContext } from 'react'
-import { v2v3ProjectRoute } from 'utils/routes'
import { ExportSplitsButton } from './ExportSplitsButton'
import { PaymentAddressSection } from './PaymentAddressSection/PaymentAddressSection'
diff --git a/src/packages/v2v3/components/shared/FeeTooltipLabel.tsx b/src/packages/v2v3/components/shared/FeeTooltipLabel.tsx
index 56134f3b71..ca0ddc9dfa 100644
--- a/src/packages/v2v3/components/shared/FeeTooltipLabel.tsx
+++ b/src/packages/v2v3/components/shared/FeeTooltipLabel.tsx
@@ -8,7 +8,7 @@ import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption'
import { V2V3_CURRENCY_ETH } from 'packages/v2v3/utils/currency'
import { amountSubFee, formatFee } from 'packages/v2v3/utils/math'
import { formatWad } from 'utils/format/formatNumber'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
export const FeeTooltipLabel = ({
currency,
diff --git a/src/packages/v2v3/components/shared/PayoutsTable/ConvertAmountsModal.tsx b/src/packages/v2v3/components/shared/PayoutsTable/ConvertAmountsModal.tsx
index 6f1eb9f189..8fad5cbda0 100644
--- a/src/packages/v2v3/components/shared/PayoutsTable/ConvertAmountsModal.tsx
+++ b/src/packages/v2v3/components/shared/PayoutsTable/ConvertAmountsModal.tsx
@@ -27,7 +27,7 @@ import {
} from 'redux/hooks/useEditingDistributionLimit'
import { parseWad } from 'utils/format/formatNumber'
import { formatPercent } from 'utils/format/formatPercent'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
export const ConvertAmountsModal = ({
open,
diff --git a/src/packages/v2v3/components/shared/PayoutsTable/HeaderRows.tsx b/src/packages/v2v3/components/shared/PayoutsTable/HeaderRows.tsx
index 4b3e50e921..4cf7f34c2a 100644
--- a/src/packages/v2v3/components/shared/PayoutsTable/HeaderRows.tsx
+++ b/src/packages/v2v3/components/shared/PayoutsTable/HeaderRows.tsx
@@ -8,7 +8,7 @@ import {
AddEditAllocationModalEntity,
} from 'packages/v2v3/components/shared/Allocation/AddEditAllocationModal'
import { useState } from 'react'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { usePayoutsTableContext } from './context/PayoutsTableContext'
import { usePayoutsTable } from './hooks/usePayoutsTable'
import { PayoutTableSettings } from './PayoutTableSettings'
diff --git a/src/packages/v2v3/components/shared/PayoutsTable/PayoutsTableBody.tsx b/src/packages/v2v3/components/shared/PayoutsTable/PayoutsTableBody.tsx
index d9a80f6916..367c92ad48 100644
--- a/src/packages/v2v3/components/shared/PayoutsTable/PayoutsTableBody.tsx
+++ b/src/packages/v2v3/components/shared/PayoutsTable/PayoutsTableBody.tsx
@@ -59,9 +59,9 @@ export function PayoutsTableBody() {
Address or ID
|
-
-
- |
+ {hasDistributionLimit ?
+
+ : Percent }
) : null}
{payoutSplits.map((payoutSplit, index) => (
diff --git a/src/packages/v2v3/components/shared/PayoutsTable/hooks/usePayoutsTable.tsx b/src/packages/v2v3/components/shared/PayoutsTable/hooks/usePayoutsTable.tsx
index 801b4ddacb..b326897e3f 100644
--- a/src/packages/v2v3/components/shared/PayoutsTable/hooks/usePayoutsTable.tsx
+++ b/src/packages/v2v3/components/shared/PayoutsTable/hooks/usePayoutsTable.tsx
@@ -18,6 +18,7 @@ import {
derivePayoutAmount,
ensureSplitsSumTo100Percent,
getNewDistributionLimit,
+ roundSplitPercents,
} from 'packages/v2v3/utils/distributions'
import {
MAX_DISTRIBUTION_LIMIT,
@@ -136,7 +137,7 @@ export const usePayoutsTable = () => {
function _setPayoutSplits(splits: Split[]) {
if (distributionLimitIsInfinite) {
- setPayoutSplits?.(splits)
+ setPayoutSplits?.(roundSplitPercents( {splits }))
} else {
setPayoutSplits?.(ensureSplitsSumTo100Percent({ splits }))
}
diff --git a/src/packages/v2v3/components/shared/V2V3ProjectHandleLink.tsx b/src/packages/v2v3/components/shared/V2V3ProjectHandleLink.tsx
index e21c0bd40f..916fbcf2fb 100644
--- a/src/packages/v2v3/components/shared/V2V3ProjectHandleLink.tsx
+++ b/src/packages/v2v3/components/shared/V2V3ProjectHandleLink.tsx
@@ -4,9 +4,9 @@ import ProjectLogo from 'components/ProjectLogo'
import { PV_V2 } from 'constants/pv'
import { useProjectHandleText } from 'hooks/useProjectHandleText'
import Link from 'next/link'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useMemo } from 'react'
import { twMerge } from 'tailwind-merge'
-import { v2v3ProjectRoute } from 'utils/routes'
/**
* Renders a link to a v2v3 project that displays its handle, or fallback text if handle is missing.
diff --git a/src/packages/v2v3/components/shared/V2V3ProjectLink.tsx b/src/packages/v2v3/components/shared/V2V3ProjectLink.tsx
index 4b1f767aa5..634caebe3a 100644
--- a/src/packages/v2v3/components/shared/V2V3ProjectLink.tsx
+++ b/src/packages/v2v3/components/shared/V2V3ProjectLink.tsx
@@ -1,8 +1,8 @@
import { Trans } from '@lingui/macro'
import { AllocatorBadge } from 'components/AllocatorBadge'
import Link from 'next/link'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { twMerge } from 'tailwind-merge'
-import { v2v3ProjectRoute } from 'utils/routes'
/**
* Different from V2V3ProjectHandleLink in that it doesn't use the handle. This can be used outside of V2V3ContractsContext and V2V3ProjectContext.
diff --git a/src/packages/v2v3/utils/distributions.ts b/src/packages/v2v3/utils/distributions.ts
index f39a1168ec..84485de4f2 100644
--- a/src/packages/v2v3/utils/distributions.ts
+++ b/src/packages/v2v3/utils/distributions.ts
@@ -147,6 +147,15 @@ export function ensureSplitsSumTo100Percent({
return adjustedSplits
}
+export function roundSplitPercents({ splits }: { splits: Split[]}) {
+ return splits.map((split) => {
+ return {
+ ...split,
+ percent: Math.round(split.percent),
+ }
+ })
+}
+
/**
* Adjusts exist split percents to stay the same amount when distribution limit is changed
* @param splits {Split[]} - list of current splits to have their percents adjusted
@@ -174,6 +183,7 @@ export function adjustedSplitPercents({
amount: currentAmount,
distributionLimit: parseFloat(newDistributionLimit),
})
+
const adjustedSplit = {
beneficiary: split.beneficiary,
percent: newPercent,
diff --git a/src/utils/routes.ts b/src/packages/v2v3/utils/routes.ts
similarity index 82%
rename from src/utils/routes.ts
rename to src/packages/v2v3/utils/routes.ts
index 69da8323bc..e6a0713a05 100644
--- a/src/utils/routes.ts
+++ b/src/packages/v2v3/utils/routes.ts
@@ -2,8 +2,6 @@ import { BigNumber, BigNumberish } from 'ethers'
import { V2V3SettingsPageKey } from 'packages/v2v3/components/V2V3Project/V2V3ProjectSettings/ProjectSettingsDashboard'
import qs from 'qs'
-const HELP_PAGE_HOSTNAME = 'https://docs.juicebox.money'
-
export const v2v3ProjectRoute = ({
projectId,
handle,
@@ -20,10 +18,6 @@ export const v2v3ProjectRoute = ({
return `${route}${query ? `?${qs.stringify(query)}` : ''}`
}
-export function helpPagePath(path: string): string {
- return new URL(path, HELP_PAGE_HOSTNAME).toString()
-}
-
export const settingsPagePath = (
settingsPage?: V2V3SettingsPageKey,
{
diff --git a/src/packages/v4/components/FeeTooltipLabel.tsx b/src/packages/v4/components/FeeTooltipLabel.tsx
index 317edc5d1f..f83ed38b6a 100644
--- a/src/packages/v4/components/FeeTooltipLabel.tsx
+++ b/src/packages/v4/components/FeeTooltipLabel.tsx
@@ -5,7 +5,7 @@ import CurrencySymbol from 'components/currency/CurrencySymbol'
import { Ether } from 'juice-sdk-core'
import { NativeTokenValue } from 'juice-sdk-react'
import { formatWad } from 'utils/format/formatNumber'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { V4CurrencyOption } from '../models/v4CurrencyOption'
import { V4_CURRENCY_ETH } from '../utils/currency'
import { amountSubFee } from '../utils/math'
diff --git a/src/packages/v4/components/PayoutsTable/ConvertAmountsModal.tsx b/src/packages/v4/components/PayoutsTable/ConvertAmountsModal.tsx
index f2247cff25..53a9afecb8 100644
--- a/src/packages/v4/components/PayoutsTable/ConvertAmountsModal.tsx
+++ b/src/packages/v4/components/PayoutsTable/ConvertAmountsModal.tsx
@@ -5,7 +5,7 @@ import EthereumAddress from 'components/EthereumAddress'
import { ExternalLinkWithIcon } from 'components/ExternalLinkWithIcon'
import FormattedNumberInput from 'components/inputs/FormattedNumberInput'
import { Parenthesis } from 'components/Parenthesis'
-import { JBSplit as Split, SPLITS_TOTAL_PERCENT } from 'juice-sdk-core'
+import { JBSplit as Split } from 'juice-sdk-core'
import { V4CurrencyOption } from 'packages/v4/models/v4CurrencyOption'
import { V4_CURRENCY_ETH, V4_CURRENCY_USD } from 'packages/v4/utils/currency'
@@ -23,7 +23,7 @@ import {
} from 'redux/hooks/useEditingDistributionLimit'
import { parseWad } from 'utils/format/formatNumber'
import { formatPercent } from 'utils/format/formatPercent'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import V4ProjectHandleLink from '../V4ProjectHandleLink'
export const ConvertAmountsModal = ({
@@ -46,7 +46,7 @@ export const ConvertAmountsModal = ({
const totalPayoutsPercent = useMemo(
() =>
splits
- .map(s => (s.percent.toFloat() / SPLITS_TOTAL_PERCENT) * 100)
+ .map(s => s.percent.toFloat() * 100)
.reduce((acc, curr) => acc + curr, 0),
[splits],
)
diff --git a/src/packages/v4/components/PayoutsTable/HeaderRows.tsx b/src/packages/v4/components/PayoutsTable/HeaderRows.tsx
index 9706befc4f..e11b47ecb7 100644
--- a/src/packages/v4/components/PayoutsTable/HeaderRows.tsx
+++ b/src/packages/v4/components/PayoutsTable/HeaderRows.tsx
@@ -1,10 +1,10 @@
-import { PlusCircleIcon } from '@heroicons/react/24/outline'
+import { PlusIcon } from '@heroicons/react/24/outline'
import { Trans } from '@lingui/macro'
import { Button } from 'antd'
import { ExternalLinkWithIcon } from 'components/ExternalLinkWithIcon'
import { PayoutsTableCell } from 'components/PayoutsTable/PayoutsTableCell'
import { useState } from 'react'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import {
AddEditAllocationModal,
AddEditAllocationModalEntity,
@@ -62,7 +62,8 @@ export function HeaderRows() {
setAddRecipientModalOpen(true)}
- icon={ }
+ icon={ }
+ className={'flex items-center justify-end gap-3'}
>
Add recipient
diff --git a/src/packages/v4/components/PayoutsTable/PayoutSplitRow.tsx b/src/packages/v4/components/PayoutsTable/PayoutSplitRow.tsx
index 7deaac5aaa..fdf6c567bd 100644
--- a/src/packages/v4/components/PayoutsTable/PayoutSplitRow.tsx
+++ b/src/packages/v4/components/PayoutsTable/PayoutSplitRow.tsx
@@ -121,6 +121,7 @@ export function PayoutSplitRow({
value={_value}
onChange={onAmountPercentageInputChange}
className="h-10 w-28 md:w-full"
+ max={distributionLimitIsInfinite ? 100 : undefined}
/>
setEditModalOpen(true)}
diff --git a/src/packages/v4/components/PayoutsTable/PayoutsTableBody.tsx b/src/packages/v4/components/PayoutsTable/PayoutsTableBody.tsx
index 8a5e02ef85..f61c05ef61 100644
--- a/src/packages/v4/components/PayoutsTable/PayoutsTableBody.tsx
+++ b/src/packages/v4/components/PayoutsTable/PayoutsTableBody.tsx
@@ -60,7 +60,9 @@ export function PayoutsTableBody() {
Address or ID
-
+ {hasDistributionLimit ?
+
+ : Percent }
|
) : null}
@@ -82,8 +84,8 @@ export function PayoutsTableBody() {
{/* Empty form items just to keep AntD useWatch happy */}
-
-
+
+
>
)
}
diff --git a/src/packages/v4/components/PayoutsTable/hooks/usePayoutsTable.tsx b/src/packages/v4/components/PayoutsTable/hooks/usePayoutsTable.tsx
index 6b3971cce7..d4b77219c0 100644
--- a/src/packages/v4/components/PayoutsTable/hooks/usePayoutsTable.tsx
+++ b/src/packages/v4/components/PayoutsTable/hooks/usePayoutsTable.tsx
@@ -26,7 +26,6 @@ import {
totalSplitsPercent, v4GetProjectOwnerRemainderSplit,
} from 'packages/v4/utils/v4Splits'
import { useMemo } from 'react'
-import { parseWad } from 'utils/format/formatNumber'
import { usePayoutsTableContext } from '../context/PayoutsTableContext'
export const usePayoutsTable = () => {
@@ -42,7 +41,7 @@ export const usePayoutsTable = () => {
const distributionLimitIsInfinite = useMemo(
() =>
distributionLimit === undefined ||
- parseWad(distributionLimit).eq(MAX_PAYOUT_LIMIT),
+ distributionLimit === Number(MAX_PAYOUT_LIMIT),
[distributionLimit],
)
@@ -54,7 +53,7 @@ export const usePayoutsTable = () => {
dontApplyFee?: boolean
}) =>
distributionLimitIsInfinite
- ? (payoutSplit.percent.toFloat() / ONE_BILLION) * 100
+ ? payoutSplit.percent.toFloat() * 100
: _derivePayoutAmount({ payoutSplit, dontApplyFee })
/* Total amount that leaves the treasury minus fees */
@@ -78,8 +77,9 @@ export const usePayoutsTable = () => {
}
const ownerRemainingPercentPPB =
- SPLITS_TOTAL_PERCENT - totalSplitsPercent(payoutSplits) // parts-per-billion
- const ownerRemainingAmount =
+ SPLITS_TOTAL_PERCENT - Number(totalSplitsPercent(payoutSplits)) // parts-per-billion
+
+ const ownerRemainingAmount =
distributionLimit && !distributionLimitIsInfinite
? deriveAmountAfterFee(
(ownerRemainingPercentPPB / ONE_BILLION) * distributionLimit,
@@ -194,30 +194,29 @@ export const usePayoutsTable = () => {
newSplit: AddEditAllocationModalEntity & { projectOwner: false }
}) {
const newSplitPercent = parseFloat(newSplit.amount.value)
- let newSplitPercentPPB = (newSplitPercent * ONE_BILLION) / 100
+ let newSplitPercentPPB = round((newSplitPercent * ONE_BILLION) / 100)
let adjustedSplits: Split[] = payoutSplits
let newDistributionLimit = distributionLimit
- const isProject = newSplit.projectId && newSplit.projectId !== '0x00'
+ const isProject = Boolean(newSplit.projectId && newSplit.projectId !== '0x00')
// If amounts (!distributionLimitIsInfinite), handle changing DL and split %s
- if (!distributionLimitIsInfinite) {
+ if (!distributionLimitIsInfinite && distributionLimit) {
const newAmount = isProject
? newSplitPercent
: deriveAmountBeforeFee(newSplitPercent)
// Convert the newAmount to its percentage of the new DL in parts-per-bill
- newDistributionLimit = distributionLimit
- ? getNewDistributionLimit({
+ newDistributionLimit = distributionLimit === 0 ? newAmount
+ : getNewDistributionLimit({
currentDistributionLimit: distributionLimit.toString(),
newSplitAmount: newAmount,
editingSplitPercent: 0,
ownerRemainingAmount,
})
- : newAmount
newSplitPercentPPB = round(
- (newAmount / (newDistributionLimit ?? 0)) * ONE_BILLION,
- )
+ (newAmount / (newDistributionLimit)) * ONE_BILLION,
+ )
// recalculate all split percents based on newly added split amount
if (newDistributionLimit && !distributionLimitIsInfinite) {
@@ -320,10 +319,11 @@ export const usePayoutsTable = () => {
? getNewDistributionLimit({
currentDistributionLimit: distributionLimit.toString(),
newSplitAmount: _amount,
- editingSplitPercent: editingPayoutSplit.percent.toFloat(),
+ editingSplitPercent: Number(editingPayoutSplit.percent.value),
ownerRemainingAmount,
})
: undefined // undefined means DL is infinite
+
const newSplitPercentPPB = round(
(_amount / (newDistributionLimit ?? 0)) * ONE_BILLION,
)
@@ -386,7 +386,6 @@ export const usePayoutsTable = () => {
setDistributionLimit?.(0)
_setPayoutSplits([])
}
-
return {
distributionLimit,
distributionLimitIsInfinite,
diff --git a/src/packages/v4/components/ProjectDashboard/V4PayRedeemCard/PayProjectModal/PayProjectModal.tsx b/src/packages/v4/components/ProjectDashboard/V4PayRedeemCard/PayProjectModal/PayProjectModal.tsx
index 53e4a0124c..466aede953 100644
--- a/src/packages/v4/components/ProjectDashboard/V4PayRedeemCard/PayProjectModal/PayProjectModal.tsx
+++ b/src/packages/v4/components/ProjectDashboard/V4PayRedeemCard/PayProjectModal/PayProjectModal.tsx
@@ -5,7 +5,7 @@ import { JuiceModal } from 'components/modals/JuiceModal'
import { Formik } from 'formik'
import Image from "next/legacy/image"
import { twMerge } from 'tailwind-merge'
-import { helpPagePath } from 'utils/routes'
+import { helpPagePath } from 'utils/helpPagePath'
import { MessageSection } from './components/MessageSection'
import { ReceiveSection } from './components/ReceiveSection'
import {
diff --git a/src/packages/v4/contexts/V4SettingsProvider.tsx b/src/packages/v4/contexts/V4SettingsProvider.tsx
new file mode 100644
index 0000000000..7584fdfe32
--- /dev/null
+++ b/src/packages/v4/contexts/V4SettingsProvider.tsx
@@ -0,0 +1,49 @@
+import { AppWrapper } from 'components/common/CoreAppWrapper/CoreAppWrapper'
+import { OPEN_IPFS_GATEWAY_HOSTNAME } from 'constants/ipfs'
+import { TransactionProvider } from 'contexts/Transaction/TransactionProvider'
+import { JBProjectProvider } from 'juice-sdk-react'
+import { useRouter } from 'next/router'
+import { Provider } from 'react-redux'
+import store from 'redux/store'
+import { WagmiProvider } from 'wagmi'
+import { chainNameMap } from '../utils/networks'
+import { EditCycleFormProvider } from '../views/V4ProjectSettings/EditCyclePage/EditCycleFormContext'
+import { wagmiConfig } from '../wagmiConfig'
+import V4ProjectMetadataProvider from './V4ProjectMetadataProvider'
+
+export const V4SettingsProvider: React.FC = ({
+ children,
+}) => {
+ const router = useRouter()
+
+ const { projectId: rawProjectId, chainName } = router.query
+ if (!rawProjectId) return null
+
+ const projectId = parseInt(rawProjectId as string)
+ const projectIdBigInt = BigInt(projectId)
+ const chainId = chainNameMap[chainName as string]
+
+ return (
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/packages/v4/utils/distributions.ts b/src/packages/v4/utils/distributions.ts
index d1add0d6ad..dfaa0091e8 100644
--- a/src/packages/v4/utils/distributions.ts
+++ b/src/packages/v4/utils/distributions.ts
@@ -1,7 +1,7 @@
import { BigNumber } from 'ethers'
-import { ONE_BILLION } from 'constants/numbers'
import { JBSplit as Split, SplitPortion, SPLITS_TOTAL_PERCENT } from 'juice-sdk-core'
+import round from 'lodash/round'
import { fromWad, parseWad } from 'utils/format/formatNumber'
import { isInfinitePayoutLimit } from './fundingCycle'
import {
@@ -46,7 +46,7 @@ export function derivePayoutAmount({
}) {
if (!distributionLimit) return 0
const amountBeforeFee =
- (payoutSplit.percent.toFloat() / ONE_BILLION) * distributionLimit
+ payoutSplit.percent.toFloat() * distributionLimit
if (isJuiceboxProjectSplit(payoutSplit) || dontApplyFee) return amountBeforeFee // projects dont have fee applied
return deriveAmountAfterFee(amountBeforeFee)
}
@@ -108,21 +108,23 @@ export function ensureSplitsSumTo100Percent({
splits: Split[]
}): Split[] {
// Calculate the percent total of the splits
- const currentTotal = splits.reduce((sum, split) => sum + split.percent.toFloat(), 0)
+ const currentTotal = splits.reduce((sum, split) => sum + split.percent.value, 0n)
+ const max = BigInt(SPLITS_TOTAL_PERCENT)
// If the current total is already equal to SPLITS_TOTAL_PERCENT, no adjustment needed
- if (currentTotal === SPLITS_TOTAL_PERCENT) {
+ if (currentTotal === max) {
return splits
}
// Calculate the ratio to adjust each split by
- const ratio = SPLITS_TOTAL_PERCENT / currentTotal
+ const ratio = max / currentTotal
// Adjust each split
- const adjustedSplits = splits.map(split => ({
- ...split,
- percent: new SplitPortion(Math.round(split.percent.toFloat() * ratio)),
- }))
-
+ const adjustedSplits = splits.map(split => {
+ split.percent = new SplitPortion(Math.round(split.percent.toFloat() * Number(ratio)))
+ return split
+
+ })
+
// Calculate the total after adjustment
const adjustedTotal = adjustedSplits.reduce(
(sum, split) => sum + split.percent.toFloat(),
@@ -131,16 +133,14 @@ export function ensureSplitsSumTo100Percent({
if (adjustedTotal === SPLITS_TOTAL_PERCENT) {
return adjustedSplits
}
-
// If there's STILL a difference due to rounding errors, adjust the largest split
const difference = SPLITS_TOTAL_PERCENT - adjustedTotal
const largestSplitIndex = adjustedSplits.findIndex(
split => split.percent.toFloat() === Math.max(...adjustedSplits.map(s => s.percent.toFloat())),
)
if (adjustedSplits[largestSplitIndex]) {
- adjustedSplits[largestSplitIndex].percent = new SplitPortion(adjustedSplits[largestSplitIndex].percent.toFloat() + difference)
+ adjustedSplits[largestSplitIndex].percent = new SplitPortion(round(adjustedSplits[largestSplitIndex].percent.toFloat()) + difference)
}
-
return adjustedSplits
}
@@ -166,14 +166,20 @@ export function adjustedSplitPercents({
percent: split.percent.formatPercentage(),
amount: oldDistributionLimit,
})
-
const newPercent = getDistributionPercentFromAmount({
amount: currentAmount,
distributionLimit: parseFloat(newDistributionLimit),
})
+ let newSplitPortion
+ try {
+ newSplitPortion = new SplitPortion(newPercent)
+ } catch (e) {
+ // Will be replaced by new/editing payout split
+ newSplitPortion = new SplitPortion(0)
+ }
const adjustedSplit = {
beneficiary: split.beneficiary,
- percent: new SplitPortion(newPercent),
+ percent: newSplitPortion,
preferAddToBalance: split.preferAddToBalance,
lockedUntil: split.lockedUntil,
projectId: split.projectId,
diff --git a/src/packages/v4/utils/routes.ts b/src/packages/v4/utils/routes.ts
index 44bbe5b5c7..d7ce939c40 100644
--- a/src/packages/v4/utils/routes.ts
+++ b/src/packages/v4/utils/routes.ts
@@ -1,3 +1,4 @@
+import { SettingsPageKey } from '../views/V4ProjectSettings/ProjectSettingsDashboard'
import { getChainName } from './networks'
export const v4ProjectRoute = ({
@@ -10,3 +11,20 @@ export const v4ProjectRoute = ({
const chainName = getChainName(chainId)
return `/v4/${chainName}/p/${projectId?.toString()}`
}
+
+export const settingsPagePath = (
+ {
+ projectId,
+ chainId
+ }: {
+ projectId: number
+ chainId: number
+ },
+ settingsPage?: SettingsPageKey,
+) => {
+ return `${v4ProjectRoute({
+ chainId,
+ projectId,
+ })}/settings/${settingsPage ?? ''}`
+}
+
diff --git a/src/packages/v4/utils/v4Splits.ts b/src/packages/v4/utils/v4Splits.ts
index 0d85e64b7c..9e4f413ee6 100644
--- a/src/packages/v4/utils/v4Splits.ts
+++ b/src/packages/v4/utils/v4Splits.ts
@@ -1,6 +1,7 @@
import * as constants from '@ethersproject/constants'
import { JBSplit, SplitPortion, SPLITS_TOTAL_PERCENT } from 'juice-sdk-core'
import isEqual from 'lodash/isEqual'
+import round from 'lodash/round'
import { formatWad } from 'utils/format/formatNumber'
import { Hash } from 'viem'
import { isFinitePayoutLimit } from './fundingCycle'
@@ -15,7 +16,7 @@ export const v4GetProjectOwnerRemainderSplit = (
projectOwnerAddress: Hash,
splits: JBSplit[],
): JBSplit & { isProjectOwner: true } => {
- const totalSplitPercentage = v4TotalSplitsPercent(splits)
+ const totalSplitPercentage = totalSplitsPercent(splits)
const ownerPercentage = new SplitPortion(
SPLITS_TOTAL_PERCENT - Number(totalSplitPercentage),
)
@@ -33,10 +34,10 @@ export const v4GetProjectOwnerRemainderSplit = (
/**
* Returns the sum of each split's percent in a list of splits
- * @param splits {Split[]} - list of splits to sum percents
+ * @param splits {JBSplit[]} - list of splits to sum percents
* @returns {bigint} - sum of percents in part-per-billion (max = SPLITS_TOTAL_PERCENT)
*/
-export const v4TotalSplitsPercent = (splits: JBSplit[]): bigint =>
+export const totalSplitsPercent = (splits: JBSplit[]): bigint =>
splits?.reduce((sum, split) => sum + split.percent.value, 0n) ?? 0n
// - true if the split has been removed (exists in old but not new),
@@ -238,14 +239,6 @@ export const isProjectSplit = (split: JBSplit): boolean => {
return Boolean(split.projectId) && split.projectId > 0n
}
-/**
- * Returns the sum of each split's percent in a list of splits
- * @param splits {JBSplit[]} - list of splits to sum percents
- * @returns {number} - sum of percents in part-per-billion (max = SPLITS_TOTAL_PERCENT)
- */
-export const totalSplitsPercent = (splits: JBSplit[]): number =>
- splits?.reduce((sum, split) => sum + Number(split.percent), 0) ?? 0
-
/**
* Determines if two lists of splits have any diff's within them.
* @param splits1 {JBSplit[]} - first list of splits
@@ -280,6 +273,6 @@ export function isJuiceboxProjectSplit(split: JBSplit) {
// e.g. Converts 10 to 100000000 (10% of SPLITS_TOTAL_PERCENT)
export const splitPortionFromFormattedPercent = (percentage: number): bigint => {
return percentage
- ? BigInt((percentage * SPLITS_TOTAL_PERCENT) / 100)
+ ? BigInt(round((percentage * SPLITS_TOTAL_PERCENT) / 100))
: 0n
}
diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectHeader.tsx b/src/packages/v4/views/V4ProjectDashboard/V4ProjectHeader.tsx
index 6877ea3a70..1171fdfa41 100644
--- a/src/packages/v4/views/V4ProjectDashboard/V4ProjectHeader.tsx
+++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectHeader.tsx
@@ -15,9 +15,8 @@ import Link from 'next/link'
import V4ProjectHandleLink from 'packages/v4/components/V4ProjectHandleLink'
import { useV4WalletHasPermission } from 'packages/v4/hooks/useV4WalletHasPermission'
import { V4OperatorPermission } from 'packages/v4/models/v4Permissions'
-import { v4ProjectRoute } from 'packages/v4/utils/routes'
+import { settingsPagePath, v4ProjectRoute } from 'packages/v4/utils/routes'
import { twMerge } from 'tailwind-merge'
-import { settingsPagePath } from 'utils/routes'
import { useChainId } from 'wagmi'
import { useV4ProjectHeader } from './hooks/useV4ProjectHeader'
import { ProjectHeaderStats } from './ProjectHeaderStats'
@@ -77,7 +76,7 @@ export const V4ProjectHeader = ({ className }: { className?: string }) => {
{canQueueRuleSets && (
diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/V4DistributePayoutsModal.tsx b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/V4DistributePayoutsModal.tsx
index eef3a5898c..048931eed8 100644
--- a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/V4DistributePayoutsModal.tsx
+++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/V4DistributePayoutsModal.tsx
@@ -12,7 +12,6 @@ import { useV4BalanceOfNativeTerminal } from 'packages/v4/hooks/useV4BalanceOfNa
import { useV4CurrentPayoutSplits } from 'packages/v4/hooks/useV4PayoutSplits'
import { V4CurrencyName } from 'packages/v4/utils/currency'
import { useEffect, useState } from 'react'
-import { fromWad } from 'utils/format/formatNumber'
import { useV4DistributableAmount } from './hooks/useV4DistributableAmount'
export default function V4DistributePayoutsModal({
@@ -40,18 +39,9 @@ export default function V4DistributePayoutsModal({
// TODO: const v4DistributePayoutsTx = useV4DistributePayoutsTx()
useEffect(() => {
- if (!payoutLimitAmount) return
-
- const unusedFunds = payoutLimitAmount - (usedPayoutLimit ?? 0n) ?? 0n
- const distributable = balanceOfNativeTerminal && balanceOfNativeTerminal > unusedFunds
- ? unusedFunds
- : 0n
-
- setDistributionAmount(fromWad(distributable))
+ setDistributionAmount(distributable.format())
}, [
- balanceOfNativeTerminal,
- payoutLimitAmount,
- usedPayoutLimit,
+ distributable,
])
async function executeDistributePayoutsTx() {
diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4FormatConfigurationCycleSection.ts b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4FormatConfigurationCycleSection.ts
index 23e017d81d..5bbaf06828 100644
--- a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4FormatConfigurationCycleSection.ts
+++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4FormatConfigurationCycleSection.ts
@@ -121,13 +121,13 @@ export const useV4FormatConfigurationCycleSection = ({
return pairToDatum(t`Edit deadline`, current, null)
}
- const upcomingBallotStrategy = upcomingRuleset?.approvalHook
+ const upcomingApprovalStrategy = upcomingRuleset?.approvalHook
? getApprovalStrategyByAddress(upcomingRuleset.approvalHook)
: ruleset?.approvalHook
? getApprovalStrategyByAddress(ruleset.approvalHook)
: undefined
- const upcoming = upcomingBallotStrategy?.name
+ const upcoming = upcomingApprovalStrategy?.name
return pairToDatum(t`Edit deadline`, current, upcoming)
}, [ruleset?.approvalHook, upcomingRuleset, upcomingPayoutLimitLoading])
diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4FormatConfigurationTokenSection.ts b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4FormatConfigurationTokenSection.ts
index afaf6d49c6..6f6cc9e49b 100644
--- a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4FormatConfigurationTokenSection.ts
+++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4FormatConfigurationTokenSection.ts
@@ -139,20 +139,20 @@ export const useV4FormatConfigurationTokenSection = ({
return pairToDatum(t`Redemption rate`, current, queued)
}, [upcomingRulesetMetadata, rulesetMetadata, upcomingRulesetLoading])
- const ownerTokenMintingRateDatum: ConfigurationPanelDatum = useMemo(() => {
- const currentOwnerTokenMintingRate =
+ const ownerTokenMintingDatum: ConfigurationPanelDatum = useMemo(() => {
+ const currentOwnerTokenMinting =
rulesetMetadata?.allowOwnerMinting !== undefined
? rulesetMetadata?.allowOwnerMinting
: undefined
if (upcomingRulesetMetadata === null || upcomingRulesetLoading) {
return flagPairToDatum(
t`Owner token minting`,
- currentOwnerTokenMintingRate,
+ currentOwnerTokenMinting,
null,
)
}
- const queuedOwnerTokenMintingRate =
+ const queuedOwnerTokenMinting =
upcomingRulesetMetadata?.allowOwnerMinting !== undefined ?
upcomingRulesetMetadata?.allowOwnerMinting
: rulesetMetadata?.allowOwnerMinting !== undefined ?
@@ -161,8 +161,8 @@ export const useV4FormatConfigurationTokenSection = ({
return flagPairToDatum(
t`Owner token minting`,
- currentOwnerTokenMintingRate,
- queuedOwnerTokenMintingRate,
+ currentOwnerTokenMinting,
+ queuedOwnerTokenMinting,
)
}, [rulesetMetadata?.allowOwnerMinting, upcomingRulesetMetadata, upcomingRulesetLoading])
@@ -203,13 +203,13 @@ export const useV4FormatConfigurationTokenSection = ({
reservedPercent: reservedPercentDatum,
decayPercentDatum: decayPercentDatum,
redemptionRate: redemptionRateDatum,
- ownerTokenMintingRate: ownerTokenMintingRateDatum,
+ ownerTokenMintingRate: ownerTokenMintingDatum,
tokenTransfers: tokenTransfersDatum,
}
}, [
decayPercentDatum,
totalIssuanceRateDatum,
- ownerTokenMintingRateDatum,
+ ownerTokenMintingDatum,
payerIssuanceRateDatum,
redemptionRateDatum,
reservedPercentDatum,
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/CycleDeadlineDropdown.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/CycleDeadlineDropdown.tsx
new file mode 100644
index 0000000000..20485d30c5
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/CycleDeadlineDropdown.tsx
@@ -0,0 +1,23 @@
+import { Trans } from '@lingui/macro'
+import { Form, Select } from 'antd'
+import { BallotStrategy } from 'models/ballot'
+
+export default function CycleDeadlineDropdown({
+ className,
+}: {
+ className?: string
+}) {
+ // const { cv } = useContext(V2V3ContractsContext)
+ // const ballotStrategies = ballotStrategiesFn({ cv })
+ return (
+
+
+ {[{ name: '1 day @todo', address: '0x00'}].map((strategy: BallotStrategy) => (
+
+ {strategy.name}
+
+ ))}
+
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DetailsSection.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DetailsSection.tsx
new file mode 100644
index 0000000000..afe2125b65
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DetailsSection.tsx
@@ -0,0 +1,34 @@
+import { Trans } from '@lingui/macro'
+import { EditCycleHeader } from 'components/Project/ProjectSettings/EditCycleHeader'
+import { CYCLE_EXPLANATION } from 'components/strings'
+import CycleDeadlineDropdown from './CycleDeadlineDropdown'
+import { DetailsSectionAdvanced } from './DetailsSectionAdvanced'
+import { DurationFields } from './DurationFields'
+
+export function DetailsSection() {
+ return (
+ <>
+
+ Cycle duration}
+ description={CYCLE_EXPLANATION}
+ />
+
+
+
+ Edit deadline}
+ description={
+
+ Edits to cycles must occur before the selected period. Deadlines
+ ensure your contributors are informed of upcoming changes to your
+ project's configuration.
+
+ }
+ />
+
+
+
+ >
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DetailsSectionAdvanced.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DetailsSectionAdvanced.tsx
new file mode 100644
index 0000000000..e71d9f26fa
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DetailsSectionAdvanced.tsx
@@ -0,0 +1,50 @@
+import { Trans } from '@lingui/macro'
+import { Form } from 'antd'
+import { AdvancedDropdown } from 'components/Project/ProjectSettings/AdvancedDropdown'
+import TooltipLabel from 'components/TooltipLabel'
+import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
+import {
+ CONTROLLER_CONFIG_EXPLANATION,
+ TERMINAL_CONFIG_EXPLANATION,
+ TERMINAL_MIGRATION_EXPLANATION
+} from 'components/strings'
+
+export function DetailsSectionAdvanced() {
+ return (
+
+
+ Disable payments to this project } />
+
+
+ Enable payment terminal configurations}
+ />
+ }
+ />
+
+
+ Enable payment terminal migrations}
+ />
+ }
+ />
+
+
+ Enable controller configurations}
+ />
+ }
+ />
+
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DurationFields.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DurationFields.tsx
new file mode 100644
index 0000000000..236669a547
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DurationFields.tsx
@@ -0,0 +1,48 @@
+import { Trans } from '@lingui/macro'
+import TooltipLabel from 'components/TooltipLabel'
+import { durationOptions } from 'components/inputs/DurationInput'
+import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
+import { CYCLE_EXPLANATION } from 'components/strings'
+import { useState } from 'react'
+import { useEditCycleFormContext } from '../EditCycleFormContext'
+import DurationInputAndSelect from './DurationInputAndSelect'
+
+export function DurationFields() {
+ const { editCycleForm, initialFormData } = useEditCycleFormContext()
+
+ const [durationEnabled, setDurationEnabled] = useState(
+ Boolean(initialFormData?.duration),
+ )
+
+ const handleSwitchChange = () => {
+ const newDurationEnabled = !durationEnabled
+ if (newDurationEnabled) {
+ editCycleForm?.setFieldsValue({ durationUnit: durationOptions()[0] })
+ } else {
+ editCycleForm?.setFieldsValue({ duration: 0 })
+ }
+ setDurationEnabled(newDurationEnabled)
+ }
+
+ return (
+
+
+ Locked cycles}
+ tip={CYCLE_EXPLANATION}
+ />
+ }
+ description={
+
+ Project configurations cannot be changed to the duration of locked
+ cycles.
+
+ }
+ />
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DurationInputAndSelect.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DurationInputAndSelect.tsx
new file mode 100644
index 0000000000..ab7f634046
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/DurationInputAndSelect.tsx
@@ -0,0 +1,39 @@
+import { Trans } from '@lingui/macro'
+import { Form } from 'antd'
+import { durationOptions } from 'components/inputs/DurationInput'
+import FormattedNumberInput from 'components/inputs/FormattedNumberInput'
+import { JuiceListbox } from 'components/inputs/JuiceListbox'
+
+export default function DurationInputAndSelect({
+ hideTitle,
+ disabled,
+}: {
+ hideTitle?: boolean
+ disabled?: boolean
+}) {
+ return (
+
+
Funding cycle duration}
+ className="w-full"
+ required
+ >
+
+
+ }>
+
+
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/index.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/index.tsx
new file mode 100644
index 0000000000..6752423004
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/DetailsSection/index.tsx
@@ -0,0 +1 @@
+export * from './DetailsSection'
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCycleFormContext.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCycleFormContext.tsx
new file mode 100644
index 0000000000..fb7f0dadad
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCycleFormContext.tsx
@@ -0,0 +1,49 @@
+import { FormInstance } from 'antd'
+import {
+ Dispatch,
+ FC,
+ PropsWithChildren,
+ SetStateAction,
+ createContext,
+ useContext,
+ useState,
+} from 'react'
+import { EditCycleFormFields } from './EditCycleFormFields'
+import { useLoadEditCycleData } from './hooks/useLoadEditCycleData'
+
+interface EditCycleDataContextType {
+ initialFormData: EditCycleFormFields | undefined
+ editCycleForm: FormInstance | undefined
+ formHasUpdated: boolean
+ setFormHasUpdated: Dispatch>
+}
+
+const EditCycleDataContext = createContext({
+ initialFormData: undefined,
+ editCycleForm: undefined,
+ formHasUpdated: false,
+ setFormHasUpdated: () => null,
+})
+
+export const useEditCycleFormContext = () => useContext(EditCycleDataContext)
+
+export const EditCycleFormProvider: FC> = ({
+ children,
+}) => {
+ const [formHasUpdated, setFormHasUpdated] = useState(false)
+
+ const { initialFormData, editCycleForm } = useLoadEditCycleData()
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCycleFormFields.ts b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCycleFormFields.ts
new file mode 100644
index 0000000000..487731c211
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCycleFormFields.ts
@@ -0,0 +1,43 @@
+import { DurationOption } from 'components/inputs/DurationInput'
+import { CurrencyName } from 'constants/currency'
+import { JBSplit } from 'juice-sdk-core'
+
+type DetailsSectionFields = {
+ duration: number
+ durationUnit: DurationOption
+ approvalHook: `0x${string}`
+ allowSetTerminals: boolean
+ allowSetController: boolean
+ allowTerminalMigration: boolean
+ pausePay: boolean
+}
+
+type PayoutsSectionFields = {
+ payoutSplits: JBSplit[]
+ payoutLimit: number | undefined // undefined = infinite limit
+ payoutLimitCurrency: CurrencyName
+ holdFees: boolean
+}
+
+type TokenSectionFields = {
+ issuanceRate: number
+ reservedPercent: number // percentage
+ reservedTokensSplits: JBSplit[]
+ decayPercent: number // "Discount / Issuance reduction rate"
+ redemptionRate: number
+ allowOwnerMinting: boolean
+ tokenTransfers: boolean
+}
+
+// type NftSectionFields = {
+// nftRewards: NftRewardsData | undefined
+// useDataSourceForRedeem: boolean
+// }
+
+export type EditCycleFormFields = DetailsSectionFields &
+ PayoutsSectionFields &
+ TokenSectionFields //&
+ // NftSectionFields
+ & {
+ memo: string | undefined
+ }
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCyclePage.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCyclePage.tsx
new file mode 100644
index 0000000000..806b9aa854
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/EditCyclePage.tsx
@@ -0,0 +1,146 @@
+import { Trans } from '@lingui/macro'
+import { Button, Form } from 'antd'
+import { ExternalLinkWithIcon } from 'components/ExternalLinkWithIcon'
+import Loading from 'components/Loading'
+import EditCycleFormSection from 'components/Project/ProjectSettings/EditCycleFormSection'
+import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
+import Link from 'next/link'
+import { useRouter } from 'next/router'
+import { settingsPagePath } from 'packages/v4/utils/routes'
+import { useContext, useEffect, useRef, useState } from 'react'
+import { helpPagePath } from 'utils/helpPagePath'
+import { useChainId } from 'wagmi'
+import { DetailsSection } from './DetailsSection'
+import { useEditCycleFormContext } from './EditCycleFormContext'
+import { PayoutsSection } from './PayoutsSection'
+// import { DetailsSection } from './DetailsSection'
+
+export function EditCyclePage() {
+ const [confirmModalOpen, setConfirmModalOpen] = useState(false)
+ const [firstRender, setFirstRender] = useState(true)
+
+ const { projectId } = useContext(ProjectMetadataContext)
+
+ const { editCycleForm, initialFormData, formHasUpdated, setFormHasUpdated } =
+ useEditCycleFormContext()
+
+ // const { error } = useEditCycleFormHasError() TODO
+
+ const router = useRouter()
+ const { section } = router.query
+
+ const chainId = useChainId()
+
+ const detailsRef = useRef(null)
+ const payoutsRef = useRef(null)
+ const tokensRef = useRef(null)
+
+ useEffect(() => {
+ if (initialFormData && firstRender) {
+ switch (section) {
+ case 'details':
+ detailsRef.current?.scrollIntoView({ behavior: 'smooth' })
+ break
+ case 'payouts':
+ payoutsRef.current?.scrollIntoView({ behavior: 'smooth' })
+ break
+ case 'tokens':
+ tokensRef.current?.scrollIntoView({ behavior: 'smooth' })
+ break
+ default:
+ break
+ }
+ setFirstRender(false)
+ }
+ }, [section, firstRender, initialFormData])
+
+ const handleFormValuesChange = () => {
+ if (!formHasUpdated) {
+ setFormHasUpdated(true)
+ }
+ }
+
+ if (!initialFormData) return
+
+ return (
+
+
+
+ Configure your cycle for transparent treasury and campaign management.
+ Any adjustments made will be published on Ethereum to inform
+ contributors.
+ {' '}
+
+ Learn more
+
+
+
+
+
+
+ {projectId ? (
+
+
+ Cancel
+
+
+ ) : null}
+ {/* */}
+ setConfirmModalOpen(true)}
+ disabled={false}//Boolean(error)}
+ >
+ Save changes
+
+ {/* */}
+
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/PayoutsSection.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/PayoutsSection.tsx
new file mode 100644
index 0000000000..d15732309e
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/PayoutsSection.tsx
@@ -0,0 +1,54 @@
+import { Trans } from '@lingui/macro'
+import { Form } from 'antd'
+import { useWatch } from 'antd/lib/form/Form'
+import { JuiceSwitch } from 'components/inputs/JuiceSwitch'
+import { AdvancedDropdown } from 'components/Project/ProjectSettings/AdvancedDropdown'
+import { CurrencyName } from 'constants/currency'
+import { JBSplit } from 'juice-sdk-core'
+import { PayoutsTable } from 'packages/v4/components/PayoutsTable/PayoutsTable'
+import { useEditCycleFormContext } from './EditCycleFormContext'
+
+export function PayoutsSection() {
+ const { editCycleForm } = useEditCycleFormContext()
+ const payoutSplits = useWatch('payoutSplits', editCycleForm) ?? []
+ const currency = useWatch('payoutLimitCurrency', editCycleForm) ?? 'ETH'
+ const payoutLimit = useWatch('payoutLimit', editCycleForm)
+
+ const setPayoutSplits = (payoutSplits: JBSplit[]) =>
+ editCycleForm?.setFieldsValue({ payoutSplits })
+
+ const setCurrency = (currency: CurrencyName) =>
+ editCycleForm?.setFieldsValue({
+ payoutLimitCurrency: currency,
+ })
+
+ const setPayoutLimit = (payoutLimit: number | undefined) =>
+ editCycleForm?.setFieldsValue({ payoutLimit })
+
+ return (
+
+
+
+ {/* "Enable unlimited payouts" switch? */}
+
+ Hold fees in project}
+ description={
+
+ Fees are held in the project instead of being processed
+ automatically.
+
+ }
+ />
+
+
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/hooks/useEditCycleFormHasError.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/hooks/useEditCycleFormHasError.tsx
new file mode 100644
index 0000000000..764645c9b2
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/hooks/useEditCycleFormHasError.tsx
@@ -0,0 +1,33 @@
+import { Trans } from '@lingui/macro'
+import { useWatch } from 'antd/lib/form/Form'
+import { SPLITS_TOTAL_PERCENT } from 'juice-sdk-core'
+import { totalSplitsPercent } from 'packages/v4/utils/v4Splits'
+import { useEditCycleFormContext } from '../EditCycleFormContext'
+
+export function useEditCycleFormHasError() {
+ const { editCycleForm } = useEditCycleFormContext()
+
+ const payoutSplits = useWatch('payoutSplits', editCycleForm) ?? []
+ const reservedSplits = useWatch('reservedTokensSplits', editCycleForm) ?? []
+
+ const payoutSplitsPercentExceedsMax =
+ totalSplitsPercent(payoutSplits) > SPLITS_TOTAL_PERCENT
+ const reservedSplitsPercentExceedsMax =
+ totalSplitsPercent(reservedSplits) > SPLITS_TOTAL_PERCENT
+
+ let error: JSX.Element | undefined
+
+ if (payoutSplitsPercentExceedsMax && reservedSplitsPercentExceedsMax) {
+ error = (
+ Payout and reserved token recipients cannot exceed 100%
+ )
+ } else if (payoutSplitsPercentExceedsMax) {
+ error = Payouts cannot exceed 100%
+ } else if (reservedSplitsPercentExceedsMax) {
+ error = Reserved token recipients cannot exceed 100%
+ }
+
+ return {
+ error,
+ }
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/EditCyclePage/hooks/useLoadEditCycleData.tsx b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/hooks/useLoadEditCycleData.tsx
new file mode 100644
index 0000000000..d33170692e
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/EditCyclePage/hooks/useLoadEditCycleData.tsx
@@ -0,0 +1,97 @@
+import { Form } from 'antd'
+import { useEffect, useState } from 'react'
+import {
+ deriveDurationOption,
+ deriveDurationUnit,
+ secondsToOtherUnit,
+} from 'utils/format/formatTime'
+
+import { useJBRuleset, useJBRulesetMetadata } from 'juice-sdk-react'
+import { useJBUpcomingRuleset } from 'packages/v4/hooks/useJBUpcomingRuleset'
+import { usePayoutLimit } from 'packages/v4/hooks/usePayoutLimit'
+import { useV4CurrentPayoutSplits } from 'packages/v4/hooks/useV4PayoutSplits'
+import { useV4ReservedSplits } from 'packages/v4/hooks/useV4ReservedSplits'
+import { V4CurrencyName } from 'packages/v4/utils/currency'
+import { EditCycleFormFields } from '../EditCycleFormFields'
+
+/** Loads project FC data directly into an AntD form instance */
+export const useLoadEditCycleData = () => {
+ const [initialFormData, setInitialFormData] = useState<
+ EditCycleFormFields | undefined
+ >(undefined)
+
+ const { data: ruleset } = useJBRuleset()
+ const { data: rulesetMetadata } = useJBRulesetMetadata()
+ const {
+ ruleset: upcomingRuleset,
+ } = useJBUpcomingRuleset()
+
+ const { splits: reservedTokensSplits } = useV4ReservedSplits()
+ const { splits: payoutSplits } = useV4CurrentPayoutSplits()
+ const { data: payoutLimit } = usePayoutLimit()
+
+ const [editCycleForm] = Form.useForm()
+
+ useEffect(() => {
+ if (
+ ruleset &&
+ rulesetMetadata
+ ) {
+ const duration = Number(ruleset.duration)
+
+ const issuanceRate = upcomingRuleset?.weight.toFloat() ?? 0
+ const reservedPercent = rulesetMetadata.reservedPercent.formatPercentage()
+ // : DefaultTokenSettings.reservedTokensPercentage
+
+ const decayPercent = ruleset.decayPercent.formatPercentage()
+ // : DefaultTokenSettings.discountRate
+
+ const redemptionRate = rulesetMetadata.redemptionRate.formatPercentage()
+ // : DefaultTokenSettings.redemptionRate
+
+ const allowOwnerMinting = rulesetMetadata.allowOwnerMinting
+ // : DefaultTokenSettings.tokenMinting
+
+ const tokenTransfers = !rulesetMetadata.pauseCreditTransfers
+ // : DefaultTokenSettings.pauseTransfers
+
+ const formData: EditCycleFormFields = {
+ duration: secondsToOtherUnit({
+ duration,
+ unit: deriveDurationUnit(duration),
+ }),
+ durationUnit: deriveDurationOption(duration),
+ approvalHook: ruleset.approvalHook,
+ allowSetTerminals:
+ rulesetMetadata.allowSetTerminals,
+ allowSetController:
+ rulesetMetadata.allowSetController,
+ allowTerminalMigration:
+ rulesetMetadata.allowTerminalMigration,
+ pausePay: rulesetMetadata.pausePay,
+ payoutSplits,
+ payoutLimit: payoutLimit ? Number(payoutLimit.amount) : undefined, // TODO: format
+ payoutLimitCurrency: V4CurrencyName(payoutLimit?.currency) ?? 'ETH',
+ holdFees: rulesetMetadata?.holdFees,
+ issuanceRate,
+ reservedPercent,
+ reservedTokensSplits,
+ decayPercent,
+ redemptionRate,
+ allowOwnerMinting,
+ tokenTransfers,
+ // nftRewards: currentProjectData.nftRewards,
+ // useDataSourceForRedeem:
+ // rulesetMetadata.useDataSourceForRedeem,
+ memo: '',
+ }
+ setInitialFormData(formData)
+ editCycleForm.setFieldsValue(formData)
+ }
+ }, [ruleset, rulesetMetadata])
+
+ return {
+ initialFormData,
+ editCycleForm,
+ }
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/ProjectDetailsSettingsPage/ProjectDetailsSettingsPage.tsx b/src/packages/v4/views/V4ProjectSettings/ProjectDetailsSettingsPage/ProjectDetailsSettingsPage.tsx
new file mode 100644
index 0000000000..7adeacfc05
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/ProjectDetailsSettingsPage/ProjectDetailsSettingsPage.tsx
@@ -0,0 +1,145 @@
+import { useForm } from 'antd/lib/form/Form'
+import { ProjectDetailsForm, ProjectDetailsFormFields } from 'components/Project/ProjectSettings/ProjectDetailsForm'
+import { PROJECT_PAY_CHARACTER_LIMIT } from 'constants/numbers'
+import { PV_V2 } from 'constants/pv'
+import { ProjectMetadataContext } from 'contexts/ProjectMetadataContext'
+import { uploadProjectMetadata } from 'lib/api/ipfs'
+import { revalidateProject } from 'lib/api/nextjs'
+
+import { useEditProjectDetailsTx } from 'packages/v2v3/hooks/transactor/useEditProjectDetailsTx'
+import { useCallback, useContext, useEffect, useState } from 'react'
+import { withoutHttps } from 'utils/http'
+import { emitInfoNotification } from 'utils/notifications'
+
+export function ProjectDetailsSettingsPage() {
+ const { projectId } = useContext(ProjectMetadataContext)
+ const { projectMetadata, refetchProjectMetadata } = useContext(
+ ProjectMetadataContext,
+ )
+
+ const [loadingSaveChanges, setLoadingSaveChanges] = useState()
+ const [projectForm] = useForm()
+
+ const editV2ProjectDetailsTx = useEditProjectDetailsTx() // v4Todo: V4 tx
+
+ const onProjectFormSaved = useCallback(async () => {
+ setLoadingSaveChanges(true)
+
+ const fields = projectForm.getFieldsValue(true)
+
+ const uploadedMetadata = await uploadProjectMetadata({
+ ...projectMetadata,
+ name: fields.name,
+ description: fields.description,
+ projectTagline: fields.projectTagline,
+ projectRequiredOFACCheck: fields.projectRequiredOFACCheck,
+ logoUri: fields.logoUri,
+ coverImageUri: fields.coverImageUri,
+ infoUri: fields.infoUri,
+ twitter: fields.twitter,
+ discord: fields.discord,
+ telegram: fields.telegram,
+ payButton: fields.payButton.substring(0, PROJECT_PAY_CHARACTER_LIMIT), // Enforce limit
+ payDisclosure: fields.payDisclosure,
+ tags: fields.tags,
+ })
+
+ if (!uploadedMetadata.Hash) {
+ setLoadingSaveChanges(false)
+ return
+ }
+
+ const txSuccess = await editV2ProjectDetailsTx(
+ {
+ cid: uploadedMetadata.Hash,
+ },
+ {
+ onConfirmed: async () => {
+ setLoadingSaveChanges(false)
+
+ emitInfoNotification('Project details saved', {
+ description: 'Your project details have been saved.',
+ })
+
+ if (projectId) {
+ await revalidateProject({
+ pv: PV_V2,
+ projectId: String(projectId),
+ })
+ }
+ refetchProjectMetadata()
+ },
+ onError: () => {
+ setLoadingSaveChanges(false)
+ },
+ onCancelled: () => {
+ setLoadingSaveChanges(false)
+ },
+ },
+ )
+
+ if (!txSuccess) {
+ setLoadingSaveChanges(false)
+ }
+ }, [
+ editV2ProjectDetailsTx,
+ projectForm,
+ projectId,
+ refetchProjectMetadata,
+ projectMetadata,
+ ])
+
+ const resetProjectForm = useCallback(() => {
+ const infoUri = withoutHttps(projectMetadata?.infoUri ?? '')
+ const discord = withoutHttps(projectMetadata?.discord ?? '')
+ const telegram = withoutHttps(projectMetadata?.telegram ?? '')
+ projectForm.setFieldsValue({
+ name: projectMetadata?.name ?? '',
+ infoUri,
+ logoUri: projectMetadata?.logoUri ?? '',
+ coverImageUri: projectMetadata?.coverImageUri ?? '',
+ description: projectMetadata?.description ?? '',
+ projectTagline: projectMetadata?.projectTagline ?? '',
+ projectRequiredOFACCheck:
+ projectMetadata?.projectRequiredOFACCheck ?? false,
+ twitter: projectMetadata?.twitter ?? '',
+ discord,
+ telegram,
+ payButton: projectMetadata?.payButton ?? '',
+ payDisclosure: projectMetadata?.payDisclosure ?? '',
+ tags: projectMetadata?.tags ?? [],
+ })
+ }, [
+ projectForm,
+ projectMetadata?.name,
+ projectMetadata?.infoUri,
+ projectMetadata?.logoUri,
+ projectMetadata?.coverImageUri,
+ projectMetadata?.description,
+ projectMetadata?.projectTagline,
+ projectMetadata?.projectRequiredOFACCheck,
+ projectMetadata?.twitter,
+ projectMetadata?.discord,
+ projectMetadata?.telegram,
+ projectMetadata?.payButton,
+ projectMetadata?.payDisclosure,
+ projectMetadata?.tags,
+ ])
+
+ // initially fill form with any existing redux state
+ useEffect(() => {
+ // Bug with antd - required to make sure form is reset after initial render
+ setTimeout(() => {
+ resetProjectForm()
+ }, 0)
+ }, [resetProjectForm])
+
+ return (
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx
new file mode 100644
index 0000000000..71dc282669
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsContent.tsx
@@ -0,0 +1,120 @@
+import { ChevronRightIcon } from '@heroicons/react/20/solid'
+import { ArrowLeftIcon } from '@heroicons/react/24/outline'
+import { Trans, t } from '@lingui/macro'
+import { Button, Layout } from 'antd'
+import Link from 'next/link'
+import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
+import { useContext, useMemo } from 'react'
+import { twJoin } from 'tailwind-merge'
+import { isZeroAddress } from 'utils/address'
+import { EditCyclePage } from './EditCyclePage/EditCyclePage'
+import { useSettingsPagePath } from './hooks/useSettingsPagePath'
+import { ProjectDetailsSettingsPage } from './ProjectDetailsSettingsPage/ProjectDetailsSettingsPage'
+import { SettingsPageKey } from './ProjectSettingsDashboard'
+import { ProjectSettingsLayout } from './ProjectSettingsLayout'
+
+const SettingsPageComponents: {
+ [k in SettingsPageKey]: () => JSX.Element | null
+} = {
+ general: ProjectDetailsSettingsPage,
+ handle: () => null, //ProjectHandleSettingsPage,
+ cycle: EditCyclePage,
+ // nfts: () => null, //EditNftsPage,
+ payouts: () => null, //PayoutsSettingsPage,
+ reservedtokens: () => null, //ReservedTokensSettingsPage,
+ transferownership: () => null, //TransferOwnershipSettingsPage,
+ archiveproject: () => null, //ArchiveProjectSettingsPage,
+ heldfees: () => null, //ProcessHeldFeesPage,
+ createerc20: () => null, //CreateErc20TokenSettingsPage,
+}
+
+const V4SettingsPageKeyTitleMap = (
+ hasExistingNfts: boolean,
+): {
+ [k in SettingsPageKey]: string
+} => ({
+ general: t`General`,
+ handle: t`Project handle`,
+ cycle: t`Cycle configuration`,
+ payouts: t`Payouts`,
+ reservedtokens: t`Reserved token recipients`,
+ // nfts: hasExistingNfts ? t`Edit NFT collection` : t`Launch New NFT Collection`,
+ transferownership: t`Transfer ownership`,
+ archiveproject: t`Archive project`,
+ heldfees: t`Process held fees`,
+ createerc20: t`Create ERC-20 token`,
+})
+
+function Breadcrumbs({
+ pageTitle,
+ settingsPageKey,
+ className,
+}: {
+ pageTitle: string
+ settingsPageKey: SettingsPageKey
+ className: string
+}) {
+ return (
+
+
+
+ Manage
+
+
+
+
+
+
+
+ {pageTitle}
+
+
+
+ )
+}
+
+export function ProjectSettingsContent({
+ settingsPageKey,
+}: {
+ settingsPageKey: SettingsPageKey
+}) {
+ const { fundingCycleMetadata } = useContext(V2V3ProjectContext)
+ const hasExistingNfts = !isZeroAddress(fundingCycleMetadata?.dataSource)
+
+ const ActiveSettingsPage = useMemo(
+ () => SettingsPageComponents[settingsPageKey],
+ [settingsPageKey],
+ )
+
+ const pageTitle =
+ V4SettingsPageKeyTitleMap(hasExistingNfts)[settingsPageKey]
+
+ return (
+
+
+
+
+
+
+
+
+
+ {pageTitle}
+
+
+
+
+
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard.tsx b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard.tsx
new file mode 100644
index 0000000000..da2c5c8d41
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard.tsx
@@ -0,0 +1,207 @@
+import { Trans } from '@lingui/macro'
+import { Button } from 'antd'
+import EthereumAddress from 'components/EthereumAddress'
+import Loading from 'components/Loading'
+import { NativeTokenValue, useJBContractContext, useJBProjectMetadataContext } from 'juice-sdk-react'
+import Link from 'next/link'
+import { useV4BalanceOfNativeTerminal } from 'packages/v4/hooks/useV4BalanceOfNativeTerminal'
+import useProjectOwnerOf from 'packages/v4/hooks/useV4ProjectOwnerOf'
+import { useV4WalletHasPermission } from 'packages/v4/hooks/useV4WalletHasPermission'
+import { V4OperatorPermission } from 'packages/v4/models/v4Permissions'
+import { useV4DistributableAmount } from '../V4ProjectDashboard/V4ProjectTabs/V4CyclesPayoutsPanel/hooks/useV4DistributableAmount'
+import { ProjectSettingsLayout } from './ProjectSettingsLayout'
+import { useSettingsPagePath } from './hooks/useSettingsPagePath'
+
+export type SettingsPageKey =
+ | 'general'
+ | 'handle'
+ | 'cycle'
+ // | 'nfts'
+ | 'payouts'
+ | 'reservedtokens'
+ | 'transferownership'
+ | 'archiveproject'
+ | 'heldfees'
+ | 'createerc20'
+
+function SettingsCard({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
+
+function SettingsGroupCard({
+ title,
+ subtitle,
+ children,
+}: {
+ title: string | JSX.Element
+ subtitle: string | JSX.Element
+ children: React.ReactNode
+}) {
+ return (
+
+ {title}
+ {subtitle}
+ {children}
+
+ )
+}
+
+export function ProjectSettingsDashboard() {
+ const { data: projectOwnerAddress } = useProjectOwnerOf()
+ const { data: balance, isLoading: loading } = useV4BalanceOfNativeTerminal()
+
+ const { projectId } = useJBContractContext()
+ const { metadata } = useJBProjectMetadataContext()
+
+ const { distributableAmount } = useV4DistributableAmount()
+ const projectHasErc20Token = false // @v4TODO
+ const hasIssueTicketsPermission = useV4WalletHasPermission(
+ V4OperatorPermission.MINT_TOKENS,
+ )
+
+ const projectMetadata = metadata?.data
+
+ const canCreateErc20Token = !projectHasErc20Token && hasIssueTicketsPermission
+
+ const erc20Path = useSettingsPagePath('createerc20')
+
+ return (
+
+
+
+ {projectMetadata ? (
+
+
+
+
+ {projectMetadata.name}
+
+
+ <>Project #{projectId.toString()}>
+
+
+
+
+
+ Owned by:
+
+
+
+
+
+ Project balance
+
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ Available payout
+
+
+ {!loading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ Edit payout
+
+
+
+
+
+
+
+
+ Project settings
+
+
+
+
General}
+ subtitle={Update basic project details }
+ >
+
+
+
+ Basic details
+
+
+ {/*
+
+ Project handle
+
+ */}
+
+
+
+
Cycle configuration}
+ subtitle={
+ Make changes to your cycle settings and rules
+ }
+ >
+
+ Edit next cycle
+
+
+
Tools}
+ subtitle={Extended functionality for project owners }
+ >
+
+ {canCreateErc20Token && (
+
+
+ Create ERC-20 Token
+
+
+ )}
+
+
+ Process held fees
+
+
+
+
+
Manage}
+ subtitle={Manage your project's state and ownership }
+ >
+
+
+
+ Transfer ownership
+
+
+
+
+ Archive
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/ProjectSettingsLayout.tsx b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsLayout.tsx
new file mode 100644
index 0000000000..8ed7317947
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/ProjectSettingsLayout.tsx
@@ -0,0 +1,35 @@
+import { Cog6ToothIcon, XMarkIcon } from '@heroicons/react/24/solid'
+import { Trans } from '@lingui/macro'
+import { useJBContractContext } from 'juice-sdk-react'
+import Link from 'next/link'
+import { v4ProjectRoute } from 'packages/v4/utils/routes'
+import { useChainId } from 'wagmi'
+
+export const ProjectSettingsLayout: React.FC = ({
+ children,
+}) => {
+ const { projectId } = useJBContractContext()
+ const chainId = useChainId()
+
+ return (
+ <>
+
+
+ {children}
+ >
+ )
+}
diff --git a/src/packages/v4/views/V4ProjectSettings/hooks/useSettingsPagePath.ts b/src/packages/v4/views/V4ProjectSettings/hooks/useSettingsPagePath.ts
new file mode 100644
index 0000000000..01e84b44b7
--- /dev/null
+++ b/src/packages/v4/views/V4ProjectSettings/hooks/useSettingsPagePath.ts
@@ -0,0 +1,11 @@
+import { useJBContractContext } from 'juice-sdk-react'
+import { settingsPagePath } from 'packages/v4/utils/routes'
+import { useChainId } from 'wagmi'
+import { SettingsPageKey } from '../ProjectSettingsDashboard'
+
+export function useSettingsPagePath(key?: SettingsPageKey) {
+ const { projectId } = useJBContractContext()
+ const chainId = useChainId()
+
+ return settingsPagePath({ projectId: Number(projectId), chainId }, key, )
+}
diff --git a/src/pages/v2/p/[projectId]/safe/index.tsx b/src/pages/v2/p/[projectId]/safe/index.tsx
index 7eb75594a9..82a6f23c87 100644
--- a/src/pages/v2/p/[projectId]/safe/index.tsx
+++ b/src/pages/v2/p/[projectId]/safe/index.tsx
@@ -5,9 +5,9 @@ import { TransactionProvider } from 'contexts/Transaction/TransactionProvider'
import { useRouter } from 'next/router'
import { V2V3ProjectContext } from 'packages/v2v3/contexts/Project/V2V3ProjectContext'
import { V2V3ProjectPageProvider } from 'packages/v2v3/contexts/V2V3ProjectPageProvider'
+import { v2v3ProjectRoute } from 'packages/v2v3/utils/routes'
import { useContext } from 'react'
import globalGetServerSideProps from 'utils/next-server/globalGetServerSideProps'
-import { v2v3ProjectRoute } from 'utils/routes'
function V2V3ProjectSafeDashboard() {
const { handle, projectOwnerAddress } = useContext(V2V3ProjectContext)
diff --git a/src/pages/v4/[chainName]/p/[projectId]/settings/[settingsPage].tsx b/src/pages/v4/[chainName]/p/[projectId]/settings/[settingsPage].tsx
new file mode 100644
index 0000000000..2921a2f462
--- /dev/null
+++ b/src/pages/v4/[chainName]/p/[projectId]/settings/[settingsPage].tsx
@@ -0,0 +1,22 @@
+import { useRouter } from 'next/router'
+import { V4SettingsProvider } from 'packages/v4/contexts/V4SettingsProvider'
+import { ProjectSettingsContent } from 'packages/v4/views/V4ProjectSettings/ProjectSettingsContent'
+import { SettingsPageKey } from 'packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard'
+import globalGetServerSideProps from 'utils/next-server/globalGetServerSideProps'
+
+export default function V4CycleSettingsPage() {
+ const router = useRouter()
+
+ const { settingsPage } = router.query
+ if (!settingsPage) return null
+
+ return (
+
+
+
+ )
+}
+
+export const getServerSideProps = globalGetServerSideProps
diff --git a/src/pages/v4/[chainName]/p/[projectId]/settings/index.tsx b/src/pages/v4/[chainName]/p/[projectId]/settings/index.tsx
new file mode 100644
index 0000000000..527f24677f
--- /dev/null
+++ b/src/pages/v4/[chainName]/p/[projectId]/settings/index.tsx
@@ -0,0 +1,13 @@
+import { V4SettingsProvider } from 'packages/v4/contexts/V4SettingsProvider'
+import { ProjectSettingsDashboard } from 'packages/v4/views/V4ProjectSettings/ProjectSettingsDashboard'
+import globalGetServerSideProps from 'utils/next-server/globalGetServerSideProps'
+
+export default function V4ProjectSettingsPage() {
+ return (
+
+
+
+ )
+}
+
+export const getServerSideProps = globalGetServerSideProps
diff --git a/src/utils/helpPagePath.ts b/src/utils/helpPagePath.ts
new file mode 100644
index 0000000000..75224bc717
--- /dev/null
+++ b/src/utils/helpPagePath.ts
@@ -0,0 +1,5 @@
+const HELP_PAGE_HOSTNAME = 'https://docs.juicebox.money'
+
+export function helpPagePath(path: string): string {
+ return new URL(path, HELP_PAGE_HOSTNAME).toString()
+}
diff --git a/src/utils/sgDbProjects.ts b/src/utils/sgDbProjects.ts
index 324a499df3..d696e65d0f 100644
--- a/src/utils/sgDbProjects.ts
+++ b/src/utils/sgDbProjects.ts
@@ -228,8 +228,6 @@ export function formatSgProjectsForUpdate({
return propertiesToUpdate.length
})
- // console.log('asdf', { changedSubgraphProjects })
-
return {
subgraphProjects,
updatedProperties,