diff --git a/public/assets/images/settings/no-nfts.webp b/public/assets/images/settings/no-nfts.webp new file mode 100644 index 0000000000..30db9dd9ae Binary files /dev/null and b/public/assets/images/settings/no-nfts.webp differ diff --git a/src/components/Create/Create.tsx b/src/components/Create/Create.tsx index 1741f23c2c..b778396280 100644 --- a/src/components/Create/Create.tsx +++ b/src/components/Create/Create.tsx @@ -12,8 +12,8 @@ import { NetworkName } from 'models/networkName' import Link from 'next/link' import { useRouter } from 'next/router' import { + AddNftCollectionForm, FundingCyclesPage, - NftRewardsPage, ProjectDetailsPage, ProjectTokenPage, ReconfigurationRulesPage, @@ -124,7 +124,7 @@ export function Create() { Reward your supporters with custom NFTs. } > - + } /> -
+
{ +export const AddNftCollectionForm = ({ + okButton, +}: { + okButton?: React.ReactNode +}) => { useSetCreateFurthestPageReached('nftRewards') const { form, initialValues } = useNftRewardsForm() const lockPageRulesWrapper = useLockPageRulesWrapper() const { goToNextPage } = useContext(PageContext) - const postPayModal = useModal() - const postPayModalData = useAppSelector( - state => state.editingV2Project.nftRewards.postPayModal, - ) - const hasNfts = !!Form.useWatch('rewards', form)?.length return ( @@ -166,74 +157,7 @@ export const NftRewardsPage = () => { header={} hideDivider > -
-

- - Show your supporters a pop-up with a message and a link - after they receive an NFT. You can use this to direct - supporters to your project's website, a Discord server, - or somewhere else. - -

- - The message that will be shown to supporters after - they receive an NFT. - - } - /> - } - > - - - - The text on the button at the bottom of the - pop-up. You can preview this below. - - } - /> - } - > - - - - Supporters will be sent to this page if they click - the button on your pop-up. You can preview this - below. - - } - /> - } - extra={t`If you leave this blank, the button will close the pop-up.`} - > - - - } - onClick={postPayModal.open} - > - Preview - -
+ { header={} hideDivider > - - - - - - +
)}
- + {okButton ?? }
@@ -273,14 +186,6 @@ export const NftRewardsPage = () => {
- - {postPayModalData && ( - - )} ) } diff --git a/src/components/Create/components/pages/NftRewards/NftAdvancedFormItems.tsx b/src/components/Create/components/pages/NftRewards/NftAdvancedFormItems.tsx new file mode 100644 index 0000000000..e035f83fae --- /dev/null +++ b/src/components/Create/components/pages/NftRewards/NftAdvancedFormItems.tsx @@ -0,0 +1,26 @@ +import { t } from '@lingui/macro' +import { Form } from 'antd' +import { JuiceSwitch } from 'components/inputs/JuiceSwitch' +import { + PREVENT_OVERSPENDING_EXPLANATION, + USE_DATASOURCE_FOR_REDEEM_EXPLANATION, +} from 'components/strings' + +export function NftAdvancedFormItems() { + return ( + <> + + + + + + + + ) +} diff --git a/src/components/Create/components/pages/NftRewards/NftPaymentSuccessFormItems.tsx b/src/components/Create/components/pages/NftRewards/NftPaymentSuccessFormItems.tsx new file mode 100644 index 0000000000..b22f35cab1 --- /dev/null +++ b/src/components/Create/components/pages/NftRewards/NftPaymentSuccessFormItems.tsx @@ -0,0 +1,100 @@ +import { EyeOutlined } from '@ant-design/icons' +import { Trans, t } from '@lingui/macro' +import { Form } from 'antd' +import { NftPostPayModal } from 'components/NftRewards/NftPostPayModal' +import TooltipLabel from 'components/TooltipLabel' +import { CreateButton } from 'components/buttons/CreateButton' +import { JuiceTextArea } from 'components/inputs/JuiceTextArea' +import { JuiceInput } from 'components/inputs/JuiceTextInput' +import { useModal } from 'hooks/useModal' +import { useAppSelector } from 'redux/hooks/useAppSelector' + +export function NftPaymentSuccessFormItems({ + hidePreview, +}: { + hidePreview?: boolean +}) { + const postPayModal = useModal() + const postPayModalData = useAppSelector( + state => state.editingV2Project.nftRewards.postPayModal, + ) + return ( + <> +
+

+ + Show your supporters a pop-up with a message and a link after they + receive an NFT. You can use this to direct supporters to your + project's website, a Discord server, or somewhere else. + +

+ + The message that will be shown to supporters after they + receive an NFT. + + } + /> + } + > + + + + The text on the button at the bottom of the pop-up. You can + preview this below. + + } + /> + } + > + + + + Supporters will be sent to this page if they click the button + on your pop-up. You can preview this below. + + } + /> + } + extra={t`If you leave this blank, the button will close the pop-up.`} + > + + + {hidePreview ? null : ( + } + onClick={postPayModal.open} + > + Preview + + )} +
+ {postPayModalData && ( + + )} + + ) +} diff --git a/src/components/Create/components/pages/NftRewards/index.ts b/src/components/Create/components/pages/NftRewards/index.ts index 5f5f642e3c..b915789057 100644 --- a/src/components/Create/components/pages/NftRewards/index.ts +++ b/src/components/Create/components/pages/NftRewards/index.ts @@ -1 +1 @@ -export * from './NftRewardsPage' +export * from './AddNftCollectionForm' diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/ProjectSettingsContent.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/ProjectSettingsContent.tsx index 45d6fd2e0f..fd98432b22 100644 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/ProjectSettingsContent.tsx +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/ProjectSettingsContent.tsx @@ -1,11 +1,13 @@ +import * as constants from '@ethersproject/constants' 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 { V2V3SettingsPageKey } from 'components/v2v3/V2V3Project/V2V3ProjectSettings/ProjectSettingsDashboard' import { FEATURE_FLAGS } from 'constants/featureFlags' +import { V2V3ProjectContext } from 'contexts/v2v3/Project/V2V3ProjectContext' import Link from 'next/link' -import { useMemo } from 'react' +import { useContext, useMemo } from 'react' import { twJoin } from 'tailwind-merge' import { featureFlagEnabled } from 'utils/featureFlags' import { ProjectSettingsLayout } from './ProjectSettingsLayout' @@ -14,6 +16,7 @@ import { ArchiveProjectSettingsPage } from './pages/ArchiveProjectSettingsPage' import { EditNftsPage } from './pages/EditNftsPage' import { GovernanceSettingsPage } from './pages/GovernanceSettingsPage' import { EditCyclePage } from './pages/NewEditCyclePage/EditCyclePage' +import { EditNftsPage as NewEditNftsPage } from './pages/NewEditNftsPage/EditNftsPage' import { PayoutsSettingsPage } from './pages/PayoutsSettingsPage' import { ProjectDetailsSettingsPage } from './pages/ProjectDetailsSettingsPage/ProjectDetailsSettingsPage' import { ProjectHandleSettingsPage } from './pages/ProjectHandleSettingsPage' @@ -32,7 +35,9 @@ const SettingsPageComponents: { cycle: featureFlagEnabled(FEATURE_FLAGS.NEW_CYCLE_CONFIG_PAGE) ? EditCyclePage : ReconfigureFundingCycleSettingsPage, - nfts: EditNftsPage, + nfts: featureFlagEnabled(FEATURE_FLAGS.NEW_EDIT_NFTS) + ? NewEditNftsPage + : EditNftsPage, payouts: PayoutsSettingsPage, reservedtokens: ReservedTokensSettingsPage, tokenmigration: V1V2TokenMigrationSettingsPage, @@ -43,7 +48,9 @@ const SettingsPageComponents: { projectnft: ProjectNftSettingsPage, } -const V2V3SettingsPageKeyTitleMap = (): { +const V2V3SettingsPageKeyTitleMap = ( + hasExistingNfts: boolean, +): { [k in V2V3SettingsPageKey]: string } => ({ general: t`General`, @@ -51,7 +58,7 @@ const V2V3SettingsPageKeyTitleMap = (): { cycle: t`Cycle configuration`, payouts: t`Payouts`, reservedtokens: t`Reserved token recipients`, - nfts: t`Edit NFT collection`, + nfts: hasExistingNfts ? t`Edit NFT collection` : t`Launch New NFT Collection`, tokenmigration: t`Token migration`, transferownership: t`Transfer ownership`, archiveproject: t`Archive project`, @@ -99,12 +106,17 @@ export function ProjectSettingsContent({ }: { settingsPageKey: V2V3SettingsPageKey }) { + const { fundingCycleMetadata } = useContext(V2V3ProjectContext) + const hasExistingNfts = + fundingCycleMetadata?.dataSource !== constants.AddressZero + const ActiveSettingsPage = useMemo( () => SettingsPageComponents[settingsPageKey], [settingsPageKey], ) - const pageTitle = V2V3SettingsPageKeyTitleMap()[settingsPageKey] + const pageTitle = + V2V3SettingsPageKeyTitleMap(hasExistingNfts)[settingsPageKey] return ( diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditNftsPage.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditNftsPage.tsx index 8c801cbc61..095b962219 100644 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditNftsPage.tsx +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditNftsPage.tsx @@ -1,18 +1,14 @@ -import { t, Trans } from '@lingui/macro' -import { Button, Empty, Tabs } from 'antd' +import { Trans } from '@lingui/macro' +import { Button, Empty } from 'antd' import Loading from 'components/Loading' -import { FEATURE_FLAGS } from 'constants/featureFlags' import { ProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' import { V2V3ProjectContext } from 'contexts/v2v3/Project/V2V3ProjectContext' import { useHasNftRewards } from 'hooks/JB721Delegate/useHasNftRewards' import Image from 'next/image' import Link from 'next/link' import { useContext } from 'react' -import { featureFlagEnabled } from 'utils/featureFlags' import { settingsPagePath } from 'utils/routes' -import { EditCollectionDetailsSection } from './EditCollectionDetailsSection' -import { EditNftsSection } from './EditNftsSection' -import { NewEditNftsSection } from './NewEditNftsSection' +import { UpdateNftsPage } from '../NewEditNftsPage/UpdateNftsPage/UpdateNftsPage' import blueberry from '/public/assets/images/blueberry-ol.png' export function EditNftsPage() { @@ -20,23 +16,10 @@ export function EditNftsPage() { const { projectId } = useContext(ProjectMetadataContext) const { handle } = useContext(V2V3ProjectContext) - const items = [ - { label: t`NFTs`, key: 'nfts', children: }, - { - label: t`Collection details`, - key: 'collection', - children: , - }, - ] - if (hasNftsLoading) { return } - if (featureFlagEnabled(FEATURE_FLAGS.NEW_EDIT_NFTS)) { - return - } - if (!hasExistingNfts) { return (
@@ -78,5 +61,5 @@ export function EditNftsPage() { ) } - return + return } diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EnableNftsCard.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EnableNftsCard.tsx deleted file mode 100644 index 9d90224c72..0000000000 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EnableNftsCard.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Trans } from '@lingui/macro' -import { Button } from 'antd' -import { ExternalLinkWithIcon } from 'components/ProjectDashboard/components/ui/ExternalLinkWithIcon' -import { useState } from 'react' -import { helpPagePath } from 'utils/routes' -import { EditCycleHeader } from '../NewEditCyclePage/EditCycleHeader' -import { EnableNftsModal } from './EnableNftsModal' - -export function EnableNftsCard() { - const [enableNftsModalOpen, setEnableNftsModalOpen] = useState(false) - return ( - <> -
- Enable NFTs for your project} - description={ - - Juicebox mints and provides extended functionality for you to sell - or reward contributors with NFTs for your project.{' '} - - Learn more about NFTs - - - } - /> - -
- setEnableNftsModalOpen(false)} - /> - - ) -} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/NewEditNftsSection.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/NewEditNftsSection.tsx deleted file mode 100644 index d99d5ab3de..0000000000 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/NewEditNftsSection.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Trans } from '@lingui/macro' -import { Form } from 'antd' -import TooltipLabel from 'components/TooltipLabel' -import { JuiceSwitch } from 'components/inputs/JuiceSwitch' -import { ProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' -import { V2V3ProjectContext } from 'contexts/v2v3/Project/V2V3ProjectContext' -import { useNftDeployerCanReconfigure } from 'hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure' -import { useContext } from 'react' -import { AdvancedDropdown } from '../NewEditCyclePage/AdvancedDropdown' -import { EnableNftsCard } from './EnableNftsCard' -import { NftsTable } from './NftsTable' - -export function NewEditNftsSection() { - const { projectOwnerAddress } = useContext(V2V3ProjectContext) - const { projectId } = useContext(ProjectMetadataContext) - - const nftDeployerCanReconfigure = useNftDeployerCanReconfigure({ - projectId, - projectOwnerAddress, - }) - - return ( -
- {nftDeployerCanReconfigure ? : } - - - Use NFTs for redemption} - tip={ - - Contributors will redeem from the project's treasury with - their NFTs as opposed to standard project tokens. - - } - /> - } - /> - - -
- ) -} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/NftsTable.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/NftsTable.tsx deleted file mode 100644 index f6d0335fc5..0000000000 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/NftsTable.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function NftsTable() { - return <>NFTs table -} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/hooks/useEditingNfts.tsx.ts b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/hooks/useEditingNfts.ts similarity index 100% rename from src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/hooks/useEditingNfts.tsx.ts rename to src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/hooks/useEditingNfts.ts diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx index 81e4897b5f..bd8ea39420 100644 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditCyclePage/ReviewConfirmModal/ReviewConfirmModal.tsx @@ -57,7 +57,7 @@ export function ReviewConfirmModal({ open={open} title={Review & confirm} destroyOnClose - onOk={reconfigureFundingCycle} + onOk={() => reconfigureFundingCycle()} okText={Deploy changes} okButtonProps={{ disabled: !formHasChanges }} cancelButtonProps={{ hidden: true }} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/EditNftsPage.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/EditNftsPage.tsx new file mode 100644 index 0000000000..a5a08d8031 --- /dev/null +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/EditNftsPage.tsx @@ -0,0 +1,29 @@ +import * as constants from '@ethersproject/constants' +import { ProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' +import { V2V3ProjectContext } from 'contexts/v2v3/Project/V2V3ProjectContext' +import { useNftDeployerCanReconfigure } from 'hooks/JB721Delegate/contractReader/useNftDeployerCanReconfigure' +import { useContext } from 'react' +import { EnableNftsCard } from './LaunchNftCollection/EnableNftsCard' +import { LaunchNftsPage } from './LaunchNftCollection/LaunchNftsCollection' +import { UpdateNftsPage } from './UpdateNftsPage/UpdateNftsPage' + +export function EditNftsPage() { + const { projectId } = useContext(ProjectMetadataContext) + const { projectOwnerAddress, fundingCycleMetadata } = + useContext(V2V3ProjectContext) + const hasExistingNfts = + fundingCycleMetadata?.dataSource !== constants.AddressZero + + const nftDeployerCanReconfigure = useNftDeployerCanReconfigure({ + projectId, + projectOwnerAddress, + }) + + if (hasExistingNfts) { + return + } else if (!nftDeployerCanReconfigure) { + return + } else { + return + } +} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/EnableNftsCard.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/EnableNftsCard.tsx new file mode 100644 index 0000000000..c8ab9b2eed --- /dev/null +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/EnableNftsCard.tsx @@ -0,0 +1,39 @@ +import { Trans } from '@lingui/macro' +import { Button } from 'antd' +import Image from 'next/image' +import { useState } from 'react' +import { EnableNftsModal } from './EnableNftsModal' +import noNftsImage from '/public/assets/images/settings/no-nfts.webp' + +export function EnableNftsCard() { + const [enableNftsModalOpen, setEnableNftsModalOpen] = useState(false) + return ( + <> +
+ {`No + + You haven't launched an NFT collection yet. + + +
+ + setEnableNftsModalOpen(false)} + /> + + ) +} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EnableNftsModal.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/EnableNftsModal.tsx similarity index 89% rename from src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EnableNftsModal.tsx rename to src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/EnableNftsModal.tsx index 8e21603606..5ce4dcd1d9 100644 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EnableNftsModal.tsx +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/EnableNftsModal.tsx @@ -4,6 +4,7 @@ import TooltipIcon from 'components/TooltipIcon' import TransactionModal from 'components/modals/TransactionModal' import { useSetNftOperatorPermissionsTx } from 'hooks/JB721Delegate/transactor/useSetNftOperatorPermissionsTx' import { useState } from 'react' +import { reloadWindow } from 'utils/windowUtils' export function EnableNftsModal({ open, @@ -21,7 +22,7 @@ export function EnableNftsModal({ await setNftOperatorPermissionsTx(undefined, { onConfirmed: () => { setLoading(false) - onClose() + reloadWindow() }, onDone() { setLoading(false) @@ -43,8 +44,8 @@ export function EnableNftsModal({ confirmLoading={loading} > - To add NFTs to your cycle. You'll need to{' '} - grant NFT permissions before launching your new cycle. + To add NFTs to your next cycle, you'll need to{' '} + grant NFT permissions. {' '} } + /> + ) +} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/UploadAndLaunchNftsButton.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/UploadAndLaunchNftsButton.tsx new file mode 100644 index 0000000000..a416fd5115 --- /dev/null +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/UploadAndLaunchNftsButton.tsx @@ -0,0 +1,95 @@ +import { Trans } from '@lingui/macro' +import { Button } from 'antd' +import { useState } from 'react' +import { useAppSelector } from 'redux/hooks/useAppSelector' +import { pinNftCollectionMetadata, pinNftRewards } from 'utils/nftRewards' +import { + EditingFundingCycleConfig, + useEditingFundingCycleConfig, +} from '../../ReconfigureFundingCycleSettingsPage/hooks/useEditingFundingCycleConfig' +import { useReconfigureFundingCycle } from '../../ReconfigureFundingCycleSettingsPage/hooks/useReconfigureFundingCycle' + +export function UploadAndLaunchNftsButton({ + className, +}: { + className?: string +}) { + const [ipfsUploading, setIpfsUploading] = useState(false) + + const { + projectMetadata: { logoUri }, + } = useAppSelector(state => state.editingV2Project) + + const editingFundingCycleConfig = useEditingFundingCycleConfig() + const { reconfigureLoading, reconfigureFundingCycle } = + useReconfigureFundingCycle({ + editingFundingCycleConfig, + memo: 'First NFT collection', + launchedNewNfts: true, + }) + + const { + editingPayoutGroupedSplits, + editingReservedTokensGroupedSplits, + editingFundingCycleMetadata, + editingFundingCycleData, + editingFundAccessConstraints, + editingNftRewards, + editingMustStartAtOrAfter, + } = editingFundingCycleConfig + + const uploadNftsToIpfs = async () => { + setIpfsUploading(true) + const newRewardTiers = + editingFundingCycleConfig.editingNftRewards?.rewardTiers + const collectionName = + editingFundingCycleConfig.editingNftRewards?.collectionMetadata.name ?? '' + const collectionDescription = + editingFundingCycleConfig.editingNftRewards?.collectionMetadata + .description ?? '' + const collectionLogoUri = logoUri ?? '' + const collectionInfoUri = + editingFundingCycleConfig.editingNftRewards?.collectionMetadata.uri ?? '' + + const [rewardTiersCIDs, nftCollectionMetadataUri] = await Promise.all([ + newRewardTiers ? pinNftRewards(newRewardTiers) : [], + pinNftCollectionMetadata({ + collectionName, + collectionDescription, + collectionLogoUri, + collectionInfoUri, + }), + ]) + const latestEditingData: EditingFundingCycleConfig = { + editingPayoutGroupedSplits, + editingReservedTokensGroupedSplits, + editingFundingCycleMetadata, + editingFundingCycleData, + editingFundAccessConstraints, + editingNftRewards: editingNftRewards + ? { + ...editingNftRewards, + collectionMetadata: { + ...editingNftRewards.collectionMetadata, + uri: nftCollectionMetadataUri, + }, + CIDs: rewardTiersCIDs, + } + : undefined, + editingMustStartAtOrAfter, + } + reconfigureFundingCycle(latestEditingData) + setIpfsUploading(false) + } + + return ( + + ) +} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/index.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/index.tsx new file mode 100644 index 0000000000..ac2a006b48 --- /dev/null +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/LaunchNftCollection/index.tsx @@ -0,0 +1 @@ +export * from './LaunchNftsCollection' diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditCollectionDetailsSection.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditCollectionDetailsSection.tsx similarity index 93% rename from src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditCollectionDetailsSection.tsx rename to src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditCollectionDetailsSection.tsx index f2d0e4330b..85c058d5aa 100644 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditCollectionDetailsSection.tsx +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditCollectionDetailsSection.tsx @@ -8,8 +8,8 @@ import { useNftCollectionMetadata } from 'hooks/JB721Delegate/useNftCollectionMe import { useReconfigureNftCollectionMetadata } from 'hooks/v2v3/transactor/useReconfigureNftCollectionMetadata' import { NftCollectionMetadata } from 'models/nftRewards' import { useCallback, useContext, useEffect, useMemo, useState } from 'react' -import { NftCollectionDetailsFormItems } from '../../../../shared/FundingCycleConfigurationDrawers/NftDrawer/shared/NftCollectionDetailsFormItems' -import { MarketplaceFormFields } from '../../../../shared/FundingCycleConfigurationDrawers/NftDrawer/shared/formFields' +import { NftCollectionDetailsFormItems } from '../../../../../shared/FundingCycleConfigurationDrawers/NftDrawer/shared/NftCollectionDetailsFormItems' +import { MarketplaceFormFields } from '../../../../../shared/FundingCycleConfigurationDrawers/NftDrawer/shared/formFields' const useCollectionDetailsForm = () => { const [form] = useForm() diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsPostPaySection/EditNftsPostPaySection.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsPostPaySection/EditNftsPostPaySection.tsx new file mode 100644 index 0000000000..0e89354040 --- /dev/null +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsPostPaySection/EditNftsPostPaySection.tsx @@ -0,0 +1,91 @@ +import { Trans } from '@lingui/macro' +import { Button, Form } from 'antd' +import { NftPaymentSuccessFormItems } from 'components/Create/components/pages/NftRewards/NftPaymentSuccessFormItems' +import { PV_V2 } from 'constants/pv' +import { ProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' +import { useEditProjectDetailsTx } from 'hooks/v2v3/transactor/useEditProjectDetailsTx' +import { uploadProjectMetadata } from 'lib/api/ipfs' +import { revalidateProject } from 'lib/api/nextjs' +import { useCallback, useContext, useState } from 'react' +import { emitInfoNotification } from 'utils/notifications' +import { + EditNftsPostPayFormFields, + useEditNftsPostPayForm, +} from './useEditNftsPostPayForm' + +export function EditNftsPostPaySection() { + const [loading, setLoading] = useState() + const { projectMetadata, refetchProjectMetadata, projectId } = useContext( + ProjectMetadataContext, + ) + const { form, initialValues } = useEditNftsPostPayForm() + + const editProjectDetailsTx = useEditProjectDetailsTx() + + const onProjectFormSaved = useCallback(async () => { + setLoading(true) + + const fields: EditNftsPostPayFormFields = form.getFieldsValue(true) + const uploadedMetadata = await uploadProjectMetadata({ + ...projectMetadata, + nftPaymentSuccessModal: { + ctaText: fields.postPayButtonText, + ctaLink: fields.postPayButtonLink, + content: fields.postPayMessage, + }, + }) + + if (!uploadedMetadata.Hash) { + setLoading(false) + return + } + + const txSuccess = await editProjectDetailsTx( + { + cid: uploadedMetadata.Hash, + }, + { + onConfirmed: async () => { + setLoading(false) + + emitInfoNotification('NFT post-pay data changed', { + description: 'Your new NFT post-pay has been saved.', + }) + + if (projectId) { + await revalidateProject({ + pv: PV_V2, + projectId: String(projectId), + }) + } + refetchProjectMetadata() + }, + onError: () => { + setLoading(false) + }, + onCancelled: () => { + setLoading(false) + }, + }, + ) + + if (!txSuccess) { + setLoading(false) + } + }, [ + editProjectDetailsTx, + form, + projectMetadata, + projectId, + refetchProjectMetadata, + ]) + + return ( +
+ + + + ) +} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsPostPaySection/useEditNftsPostPayForm.ts b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsPostPaySection/useEditNftsPostPayForm.ts new file mode 100644 index 0000000000..b921ea3e23 --- /dev/null +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsPostPaySection/useEditNftsPostPayForm.ts @@ -0,0 +1,24 @@ +import { Form } from 'antd' +import { ProjectMetadataContext } from 'contexts/shared/ProjectMetadataContext' +import { useContext } from 'react' + +export interface EditNftsPostPayFormFields { + postPayMessage: string | undefined + postPayButtonText: string | undefined + postPayButtonLink: string | undefined +} + +export const useEditNftsPostPayForm = () => { + const { projectMetadata } = useContext(ProjectMetadataContext) + const [form] = Form.useForm() + + const initialValues: EditNftsPostPayFormFields = { + postPayMessage: projectMetadata?.nftPaymentSuccessModal?.content, + postPayButtonText: projectMetadata?.nftPaymentSuccessModal?.ctaText, + postPayButtonLink: projectMetadata?.nftPaymentSuccessModal?.ctaLink, + } + return { + form, + initialValues, + } +} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditNftsSection.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsSection.tsx similarity index 88% rename from src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditNftsSection.tsx rename to src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsSection.tsx index d0e4b9ee46..200aa44f15 100644 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/EditNftsPage/EditNftsSection.tsx +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/EditNftsSection.tsx @@ -7,7 +7,7 @@ import { useUpdateCurrentCollection } from 'components/v2v3/V2V3Project/V2V3Proj import { useHasNftRewards } from 'hooks/JB721Delegate/useHasNftRewards' import { useCallback, useState } from 'react' -import { useEditingNfts } from './hooks/useEditingNfts.tsx' +import { useEditingNfts } from '../../EditNftsPage/hooks/useEditingNfts' export function EditNftsSection() { const [submitLoading, setSubmitLoading] = useState(false) @@ -27,7 +27,11 @@ export function EditNftsSection() { setSubmitLoading(false) }, [rewardTiers, updateExistingCollection]) - if (loading) return + // this component only renders when data source is not 0x000.. + // so if there are no rewardTiers here, it's safe to assume they're still loading + const noTiers = !rewardTiers || rewardTiers.length === 0 + + if (loading || noTiers) return return ( <> diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/UpdateNftsPage.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/UpdateNftsPage.tsx new file mode 100644 index 0000000000..07b8545546 --- /dev/null +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/UpdateNftsPage.tsx @@ -0,0 +1,23 @@ +import { t } from '@lingui/macro' +import { Tabs } from 'antd' +import { EditCollectionDetailsSection } from './EditCollectionDetailsSection' +import { EditNftsPostPaySection } from './EditNftsPostPaySection/EditNftsPostPaySection' +import { EditNftsSection } from './EditNftsSection' + +export function UpdateNftsPage() { + const items = [ + { label: t`NFTs`, key: 'nfts', children: }, + { + label: t`Collection details`, + key: 'collection', + children: , + }, + { + label: t`Post-pay popup`, + key: 'post-pay', + children: , + }, + ] + + return +} diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/index.tsx b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/index.tsx new file mode 100644 index 0000000000..511e1182d5 --- /dev/null +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/NewEditNftsPage/UpdateNftsPage/index.tsx @@ -0,0 +1 @@ +export * from './UpdateNftsPage' diff --git a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/ReconfigureFundingCycleSettingsPage/hooks/useReconfigureFundingCycle.ts b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/ReconfigureFundingCycleSettingsPage/hooks/useReconfigureFundingCycle.ts index 07d039787c..d1ced0c89a 100644 --- a/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/ReconfigureFundingCycleSettingsPage/hooks/useReconfigureFundingCycle.ts +++ b/src/components/v2v3/V2V3Project/V2V3ProjectSettings/pages/ReconfigureFundingCycleSettingsPage/hooks/useReconfigureFundingCycle.ts @@ -65,111 +65,109 @@ export const useReconfigureFundingCycle = ({ const reconfigureV2V3FundingCycleWithNftsTx = useReconfigureV2V3FundingCycleWithNftsTx() - const { - editingPayoutGroupedSplits, - editingReservedTokensGroupedSplits, - editingFundingCycleMetadata, - editingFundingCycleData, - editingFundAccessConstraints, - editingNftRewards, - editingMustStartAtOrAfter, - } = editingFundingCycleConfig - - const reconfigureFundingCycle = useCallback(async () => { - setReconfigureTxLoading(true) - if ( - !( - fundingCycle && - editingFundingCycleData && - editingFundingCycleMetadata && - editingFundAccessConstraints - ) - ) { - setReconfigureTxLoading(false) - throw new Error('Error deploying project.') - } - - // Projects with NFT rewards need useDataSourceForPay to be true for NFT rewards to work - const fundingCycleMetadata = nftRewardsCids?.length - ? { - ...editingFundingCycleMetadata, - ...NFT_FUNDING_CYCLE_METADATA_OVERRIDES, - } - : editingFundingCycleMetadata - - const weight = getWeightArgument({ - currentFundingCycleWeight: fundingCycle.weight, - newFundingCycleWeight: editingFundingCycleData.weight, - }) - - const reconfigureFundingCycleData: ReconfigureFundingCycleTxParams = { - fundingCycleData: { - ...editingFundingCycleData, - weight, - }, - fundingCycleMetadata, - fundAccessConstraints: editingFundAccessConstraints, - groupedSplits: [ + // If given a latestEditingData, will use that. Else, will use redux store + const reconfigureFundingCycle = useCallback( + async (latestEditingData?: EditingFundingCycleConfig) => { + const { editingPayoutGroupedSplits, editingReservedTokensGroupedSplits, - ], - memo, - mustStartAtOrAfter: editingMustStartAtOrAfter, - } + editingFundingCycleMetadata, + editingFundingCycleData, + editingFundAccessConstraints, + editingNftRewards, + editingMustStartAtOrAfter, + } = latestEditingData ?? editingFundingCycleConfig - const txOpts = { - async onConfirmed() { - if (projectId) { - await revalidateProject({ - pv: PV_V2, - projectId: String(projectId), - }) - } + setReconfigureTxLoading(true) + if ( + !( + fundingCycle && + editingFundingCycleData && + editingFundingCycleMetadata && + editingFundAccessConstraints + ) + ) { setReconfigureTxLoading(false) - if (onComplete) { - onComplete() - } else { - reloadWindow() - } - }, - } + throw new Error('Error deploying project.') + } + + // Projects with NFT rewards need useDataSourceForPay to be true for NFT rewards to work + const fundingCycleMetadata = nftRewardsCids?.length + ? { + ...editingFundingCycleMetadata, + ...NFT_FUNDING_CYCLE_METADATA_OVERRIDES, + } + : editingFundingCycleMetadata - let txSuccessful: boolean - if (launchedNewNfts && editingNftRewards?.rewardTiers) { - txSuccessful = await reconfigureV2V3FundingCycleWithNftsTx( - { - reconfigureData: reconfigureFundingCycleData, - tiered721DelegateData: editingNftRewards, + const weight = getWeightArgument({ + currentFundingCycleWeight: fundingCycle.weight, + newFundingCycleWeight: editingFundingCycleData.weight, + }) + + const reconfigureFundingCycleData: ReconfigureFundingCycleTxParams = { + fundingCycleData: { + ...editingFundingCycleData, + weight, + }, + fundingCycleMetadata, + fundAccessConstraints: editingFundAccessConstraints, + groupedSplits: [ + editingPayoutGroupedSplits, + editingReservedTokensGroupedSplits, + ], + memo, + mustStartAtOrAfter: editingMustStartAtOrAfter, + } + + const txOpts = { + async onConfirmed() { + if (projectId) { + await revalidateProject({ + pv: PV_V2, + projectId: String(projectId), + }) + } + setReconfigureTxLoading(false) + if (onComplete) { + onComplete() + } else { + reloadWindow() + } }, - txOpts, - ) - } else { - txSuccessful = await reconfigureV2V3FundingCycleTx( - reconfigureFundingCycleData, - txOpts, - ) - } + } - if (!txSuccessful) { - setReconfigureTxLoading(false) - } - }, [ - editingFundingCycleData, - editingFundingCycleMetadata, - editingFundAccessConstraints, - reconfigureV2V3FundingCycleTx, - reconfigureV2V3FundingCycleWithNftsTx, - launchedNewNfts, - editingNftRewards, - editingPayoutGroupedSplits, - editingReservedTokensGroupedSplits, - editingMustStartAtOrAfter, - nftRewardsCids, - fundingCycle, - memo, - onComplete, - projectId, - ]) + let txSuccessful: boolean + if (launchedNewNfts && editingNftRewards?.rewardTiers) { + txSuccessful = await reconfigureV2V3FundingCycleWithNftsTx( + { + reconfigureData: reconfigureFundingCycleData, + tiered721DelegateData: editingNftRewards, + }, + txOpts, + ) + } else { + txSuccessful = await reconfigureV2V3FundingCycleTx( + reconfigureFundingCycleData, + txOpts, + ) + } + + if (!txSuccessful) { + setReconfigureTxLoading(false) + } + }, + [ + editingFundingCycleConfig, + reconfigureV2V3FundingCycleTx, + reconfigureV2V3FundingCycleWithNftsTx, + launchedNewNfts, + nftRewardsCids, + fundingCycle, + memo, + onComplete, + projectId, + ], + ) return { reconfigureLoading: reconfigureTxLoading, reconfigureFundingCycle } } diff --git a/src/locales/messages.pot b/src/locales/messages.pot index 816f2e4a88..60d8f2429a 100644 --- a/src/locales/messages.pot +++ b/src/locales/messages.pot @@ -152,9 +152,6 @@ msgstr "" msgid "Data from current cycle" msgstr "" -msgid "Contributors will redeem from the project's treasury with their NFTs as opposed to standard project tokens." -msgstr "" - msgid "You're about to add NFTs to your cycle. You'll need to <0>grant NFT permissions before deploying the new cycle" msgstr "" @@ -308,9 +305,6 @@ msgstr "" msgid "Contributions" msgstr "" -msgid "Enable NFTs for your project" -msgstr "" - msgid "Attach an image" msgstr "" @@ -1022,6 +1016,9 @@ msgstr "" msgid "<0><1>Peel manages this website — the juicebox.money frontend interface. You can reach out to Peel through the <2>Peel Discord.<3><4>JuiceboxDAO builds and governs the Juicebox fundraising protocol and other community resources. You can reach out to JuiceboxDAO through the <5>Juicebox Discord." msgstr "" +msgid "To add NFTs to your next cycle, you'll need to <0>grant NFT permissions." +msgstr "" + msgid "Email address" msgstr "" @@ -1514,6 +1511,9 @@ msgstr "" msgid "Current" msgstr "" +msgid "Save post-pay popup" +msgstr "" + msgid "Next cycle, the project will issue {0} tokens per 1 ETH. The cycle after that, the project will issue {1} tokens per 1 ETH." msgstr "" @@ -3017,9 +3017,6 @@ msgstr "" msgid "Danger Zone" msgstr "" -msgid "Juicebox mints and provides extended functionality for you to sell or reward contributors with NFTs for your project. <0>Learn more about NFTs" -msgstr "" - msgid "Create an ERC-20 token (optional)" msgstr "" @@ -3401,6 +3398,9 @@ msgstr "" msgid "Project tags" msgstr "" +msgid "Deploy NFT collection" +msgstr "" + msgid "Configure how your project will collect and spend ETH." msgstr "" @@ -3539,9 +3539,6 @@ msgstr "" msgid "Prevent NFT overspending" msgstr "" -msgid "To add NFTs to your cycle. You'll need to <0>grant NFT permissions before launching your new cycle." -msgstr "" - msgid "Pay this project to receive NFTs." msgstr "" @@ -3581,9 +3578,6 @@ msgstr "" msgid "Burn your {tokensLabel}. You won't receive ETH in return because this project has no ETH, or is using all of its ETH for payouts." msgstr "" -msgid "Use NFTs for redemption" -msgstr "" - msgid "JB vs. Kickstarter" msgstr "" @@ -4487,6 +4481,9 @@ msgstr "" msgid "Percentage to reserve" msgstr "" +msgid "Post-pay popup" +msgstr "" + msgid "All assets" msgstr "" @@ -4559,12 +4556,18 @@ msgstr "" msgid "For fundraising" msgstr "" +msgid "Launch New NFT Collection" +msgstr "" + msgid "Status" msgstr "" msgid "Redemption rate:" msgstr "" +msgid "You haven't launched an NFT collection yet." +msgstr "" + msgid "Project Token" msgstr ""