diff --git a/src/packages/v4/contexts/V4NftRewardsProvider.tsx b/src/packages/v4/contexts/V4NftRewardsProvider.tsx new file mode 100644 index 0000000000..3e782630f3 --- /dev/null +++ b/src/packages/v4/contexts/V4NftRewardsProvider.tsx @@ -0,0 +1,103 @@ +import { useJBRulesetMetadata } from 'juice-sdk-react' +import { JB721GovernanceType, NftRewardTier } from 'models/nftRewards' +import { NftRewardsContext } from 'packages/v2v3/contexts/NftRewards/NftRewardsContext' +import { useEffect, useMemo, useState } from 'react' +import { + DEFAULT_NFT_FLAGS, + DEFAULT_NFT_PRICING, + EMPTY_NFT_COLLECTION_METADATA, +} from 'redux/slices/editingV2Project' +import { CIDsOfNftRewardTiersResponse } from 'utils/nftRewards' +import { useV4NftTiers } from './useV4NftTiers' + +export const V4NftRewardsProvider: React.FC< + React.PropsWithChildren +> = ({ children }) => { + const { data } = useJBRulesetMetadata() + + const [firstLoad, setFirstLoad] = useState(true) + + const dataHookAddress = data?.dataHook + + // don't fetch stuff if there's no datasource in the first place. + const hasNftRewards = Boolean(dataHookAddress) + + // fetch NFT tier data from the contract + const { data: nftRewardTiersResponse, isLoading: nftRewardsCIDsLoading } = + useV4NftTiers({ + dataHookAddress, + shouldFetch: hasNftRewards, + }) + + // catchall to ensure nfts are never loaded if hasNftRewards is false (there's no datasource). + const tierData = hasNftRewards ? nftRewardTiersResponse ?? [] : [] + const loadedCIDs = CIDsOfNftRewardTiersResponse( + tierData.map(t => ({ encodedIPFSUri: t.encodedIPFSUri })), + ) + + // // fetch NFT metadata (its image, name etc.) from ipfs + // const { data: loadedRewardTiers, isLoading: nftRewardTiersLoading } = + // useNftRewards(tierData, projectId, dataHookAddress) + const nftRewardTiersLoading = false // todo wip + // TODO very wip - townhall demo + const rewardTiers: NftRewardTier[] = tierData.map(t => { + return { + contributionFloor: Number(t.price), // ETH or USD amount + fileUrl: '', + name: 'demo - WIP', + id: t.id, + maxSupply: undefined, + remainingSupply: undefined, + reservedRate: undefined, + beneficiary: undefined, + votingWeight: undefined, + externalLink: undefined, + description: undefined, + } + }) + const CIDs = loadedCIDs + + useEffect(() => { + // First load is always true until either nftRewardTiersLoading or nftRewardsCIDsLoading is true + if (firstLoad && (nftRewardTiersLoading || nftRewardsCIDsLoading)) { + setFirstLoad(false) + } + }, [firstLoad, nftRewardTiersLoading, nftRewardsCIDsLoading]) + + const nftsLoading = useMemo(() => { + return Boolean(firstLoad || nftRewardTiersLoading || nftRewardsCIDsLoading) + }, [firstLoad, nftRewardTiersLoading, nftRewardsCIDsLoading]) + + // fetch some other related stuff + // const { data: collectionMetadataUri } = + // useNftCollectionMetadataUri(dataSourceAddress) + // const { data: flags } = useNftFlagsOf(dataSourceAddress) + + const contextData = { + nftRewards: { + rewardTiers, + // pricing: pricing ?? DEFAULT_NFT_PRICING, // TODO wip + pricing: DEFAULT_NFT_PRICING, + // TODO: Load governance type + governanceType: JB721GovernanceType.NONE, + CIDs, + // TODO wip + collectionMetadata: { + ...EMPTY_NFT_COLLECTION_METADATA, // only load the metadata CID in the context - other data not necessary + // uri: collectionMetadataUri, + uri: '', // TODO + }, + postPayModal: undefined, + // postPayModal: projectMetadata?.nftPaymentSuccessModal, + // flags: flags ?? DEFAULT_NFT_FLAGS, TODO wip + flags: DEFAULT_NFT_FLAGS, + }, + loading: nftsLoading, + } + + return ( + + {children} + + ) +} diff --git a/src/packages/v4/contexts/useV4NftTiers.ts b/src/packages/v4/contexts/useV4NftTiers.ts new file mode 100644 index 0000000000..e06a5093fc --- /dev/null +++ b/src/packages/v4/contexts/useV4NftTiers.ts @@ -0,0 +1,36 @@ +import { + useReadJb721TiersHookStoreAddress, + useReadJb721TiersHookStoreTiersOf, +} from 'juice-sdk-react' +import { Address } from 'viem' + +export function useV4NftTiers({ + dataHookAddress, + limit, + shouldFetch, +}: { + dataHookAddress: Address | undefined + limit?: number + shouldFetch?: boolean +}) { + const { data: store } = useReadJb721TiersHookStoreAddress({ + address: dataHookAddress, + }) + + // send null when project has no dataSource, so the fetch doesn't execute. + const args = + shouldFetch && dataHookAddress + ? ([ + dataHookAddress, + [], // _categories + false, // _includeResolvedUri, return in each tier a result from a tokenUriResolver if one is included in the delegate + 0n, // _startingId + BigInt(limit ?? 69), // MAX_NFT_REWARD_TIERS + ] as const) + : undefined + + return useReadJb721TiersHookStoreTiersOf({ + address: store, + args, + }) +} diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4NftRewardsPanel/V4NftRewardsPanel.tsx b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4NftRewardsPanel/V4NftRewardsPanel.tsx new file mode 100644 index 0000000000..08fae3687a --- /dev/null +++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4NftRewardsPanel/V4NftRewardsPanel.tsx @@ -0,0 +1,46 @@ +import { Trans } from '@lingui/macro' +import { NftRewardsContext } from 'packages/v2v3/contexts/NftRewards/NftRewardsContext' +import { useContext } from 'react' + +export const V4NftRewardsPanel = () => { + const x = useContext(NftRewardsContext) + const rewardTiers = x.nftRewards.rewardTiers + + return ( +
+

+ NFTs +

+ + {rewardTiers?.map((tier, i) => ( +
+ {tier.id} +
+ ))} + + {/* {!nftsLoading && rewardTiers?.length ? ( +
+ {rewardTiers?.map((tier, i) => ( +
+ handleTierSelect(tier.id, quantity)} + onDeselect={() => handleTierDeselect(tier.id)} + /> +
+ ))} +
+ ) : nftsLoading ? ( +
+ {[...Array(6)].map((_, i) => ( + + ))} +
+ ) : ( + + )} */} +
+ ) +} diff --git a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ProjectTabs.tsx b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ProjectTabs.tsx index cc99f1436c..b4defdbe5f 100644 --- a/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ProjectTabs.tsx +++ b/src/packages/v4/views/V4ProjectDashboard/V4ProjectTabs/V4ProjectTabs.tsx @@ -1,14 +1,23 @@ -import { Fragment, useEffect, useMemo, useRef, useState } from 'react' +import { + Fragment, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react' import { Tab } from '@headlessui/react' import { t } from '@lingui/macro' import { ProjectTab } from 'components/Project/ProjectTabs/ProjectTab' import { useOnScreen } from 'hooks/useOnScreen' +import { NftRewardsContext } from 'packages/v2v3/contexts/NftRewards/NftRewardsContext' import { twMerge } from 'tailwind-merge' import { useProjectPageQueries } from '../hooks/useProjectPageQueries' import V4AboutPanel from './V4AboutPanel' import { V4ActivityPanel } from './V4ActivityPanel/V4ActivityPanel' import { V4CyclesPayoutsPanel } from './V4CyclesPayoutsPanel/V4CyclesPayoutsPanel' +import { V4NftRewardsPanel } from './V4NftRewardsPanel/V4NftRewardsPanel' import { V4TokensPanel } from './V4TokensPanel/V4TokensPanel' type ProjectTabConfig = { @@ -20,7 +29,11 @@ type ProjectTabConfig = { export const V4ProjectTabs = ({ className }: { className?: string }) => { const { projectPageTab, setProjectPageTab } = useProjectPageQueries() - + const { + nftRewards: { rewardTiers }, + } = useContext(NftRewardsContext) + const hasNftRewards = (rewardTiers ?? []).length > 0 + const showNftRewards = hasNftRewards const containerRef = useRef(null) const panelRef = useRef(null) const isPanelVisible = useOnScreen(panelRef) @@ -48,12 +61,12 @@ export const V4ProjectTabs = ({ className }: { className?: string }) => { () => [ { id: 'activity', name: t`Activity`, panel: }, { id: 'about', name: t`About`, panel: }, - // { - // id: 'nft_rewards', - // name: t`NFTs`, - // panel: , - // hideTab: !showNftRewards, - // }, + { + id: 'nft_rewards', + name: t`NFTs`, + panel: , + hideTab: !showNftRewards, + }, { id: 'cycle_payouts', name: t`Cycles & Payouts`, diff --git a/src/pages/v4/[chainName]/p/[projectId]/index.tsx b/src/pages/v4/[chainName]/p/[projectId]/index.tsx index 0bf163c12e..9c491faf26 100644 --- a/src/pages/v4/[chainName]/p/[projectId]/index.tsx +++ b/src/pages/v4/[chainName]/p/[projectId]/index.tsx @@ -5,6 +5,7 @@ import { JBChainId, JBProjectProvider } from 'juice-sdk-react' import { useRouter } from 'next/router' import { ReduxProjectCartProvider } from 'packages/v4/components/ProjectDashboard/ReduxProjectCartProvider' import store from 'packages/v4/components/ProjectDashboard/redux/store' +import { V4NftRewardsProvider } from 'packages/v4/contexts/V4NftRewardsProvider' import V4ProjectMetadataProvider from 'packages/v4/contexts/V4ProjectMetadataProvider' import { useCurrentRouteChainId } from 'packages/v4/hooks/useCurrentRouteChainId' import { V4ProjectDashboard } from 'packages/v4/views/V4ProjectDashboard/V4ProjectDashboard' @@ -70,9 +71,11 @@ const Providers: React.FC< }} > - - {children} - + + + {children} + + diff --git a/src/utils/nftRewards.ts b/src/utils/nftRewards.ts index 628d98d2ee..8b030aab84 100644 --- a/src/utils/nftRewards.ts +++ b/src/utils/nftRewards.ts @@ -11,19 +11,18 @@ import { JB721GovernanceType, JB721PricingParams, JB721TierParams, - JB721TierV3, JBDeployTiered721DelegateData, JBTiered721Flags, JB_721_TIER_PARAMS_V3_1, JB_721_TIER_PARAMS_V3_2, - JB_721_TIER_V3_2, JB_DEPLOY_TIERED_721_DELEGATE_DATA_V3_1, - NftRewardTier, + NftRewardTier } from 'models/nftRewards' import { DEFAULT_NFT_MAX_SUPPLY } from 'packages/v2v3/constants/nftRewards' import { JB721DelegateVersion } from 'packages/v2v3/models/contracts' import { V2V3CurrencyOption } from 'packages/v2v3/models/currencyOption' import { decodeEncodedIpfsUri, encodeIpfsUri, ipfsUri } from 'utils/ipfs' +import { Hash } from 'viem' export function sortNftsByContributionFloor( rewardTiers: NftRewardTier[], @@ -67,7 +66,9 @@ export function getNftRewardOfFloor({ // returns an array of CIDs from a given array of RewardTier obj's export function CIDsOfNftRewardTiersResponse( - nftRewardTiersResponse: JB721TierV3[] | JB_721_TIER_V3_2[] | undefined, + nftRewardTiersResponse: { + encodedIPFSUri: Hash + }[], ): string[] { const cids = nftRewardTiersResponse