diff --git a/apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx b/apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx index 32e5714b4..a45857513 100644 --- a/apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx +++ b/apps/web/src/app/[lang]/dho/[id]/@tab/treasury/page.tsx @@ -3,6 +3,7 @@ import { AssetsSection, TransactionsSection, SpaceTabAccessWrapper, + SpacePendingRewardsSection, } from '@hypha-platform/epics'; import { getDhoPathTreasury } from './constants'; import { findSpaceBySlug } from '@hypha-platform/core/server'; @@ -26,6 +27,9 @@ export default async function TreasuryPage(props: PageProps) { spaceSlug={id} >
+ = ({ pendingRewards !== undefined ? Number(pendingRewards / 10n ** 18n) : 0; const hyphaTokenAsset = - originalAsset && pendingRewards !== undefined - ? { - ...originalAsset, - value: parsedRewardValue, - } + pendingRewards !== undefined + ? originalAsset + ? { ...originalAsset, value: parsedRewardValue } + : { ...HYPHA_REWARDS_FALLBACK, value: parsedRewardValue } : undefined; useEffect(() => { if (parsedRewardValue >= MIN_REWARD_CLAIM_VALUE) { @@ -67,6 +74,7 @@ export const PendingRewardsSection: FC = ({ !(parsedRewardValue >= MIN_REWARD_CLAIM_VALUE) || isClaiming || pendingRewards === undefined; + const onHandleClaim = useCallback(async () => { try { const txHash = await claim(); @@ -99,17 +107,24 @@ export const PendingRewardsSection: FC = ({
- {!isAuthenticated ? ( + {isLoading ? ( +
+ +
+ ) : !isAuthenticated ? (

No rewards found for this user

) : ( - <> -
- -
- {isLoading && } - +
+ +
)}
diff --git a/packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx b/packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx new file mode 100644 index 000000000..aca3b9af8 --- /dev/null +++ b/packages/epics/src/treasury/components/assets/space-pending-rewards-section.tsx @@ -0,0 +1,168 @@ +'use client'; + +import { FC, useCallback, useEffect, useState } from 'react'; +import { SectionFilter } from '@hypha-platform/ui/server'; +import { + usePendingRewards, + useSpaceDetailsWeb3Rpc, +} from '@hypha-platform/core/client'; +import { AssetCard } from './asset-card'; +import { useAssetsSection, useTokenSupply } from '../../hooks'; +import { Button } from '@hypha-platform/ui'; +import { Loader2 } from 'lucide-react'; +import { useAuthentication } from '@hypha-platform/authentication'; +import { Empty } from '../../../common'; +import { useSpaceMember } from '../../../spaces'; +import { useParams } from 'next/navigation'; +import { useSWRConfig } from 'swr'; + +const HYPHA_TOKEN_ADDRESS = '0x8b93862835C36e9689E9bb1Ab21De3982e266CD3'; +const MIN_REWARD_CLAIM_VALUE = 0.01; + +const HYPHA_REWARDS_FALLBACK = { + icon: '/placeholder/hypha-token-icon.svg', + name: 'Hypha', + symbol: 'HYPHA', + value: 0, + address: HYPHA_TOKEN_ADDRESS, +}; + +type SpacePendingRewardsSectionProps = { + web3SpaceId: number; +}; + +export const SpacePendingRewardsSection: FC< + SpacePendingRewardsSectionProps +> = ({ web3SpaceId }) => { + const { id: spaceSlug } = useParams<{ id: string }>(); + const { mutate } = useSWRConfig(); + const { isAuthenticated } = useAuthentication(); + const { spaceDetails } = useSpaceDetailsWeb3Rpc({ spaceId: web3SpaceId }); + const { isMember } = useSpaceMember({ spaceId: web3SpaceId }); + const executor = spaceDetails?.executor as `0x${string}` | undefined; + + const { filteredAssets, isLoading: isLoadingAssets } = useAssetsSection(); + const { supply: hyphaTotalSupply } = useTokenSupply( + HYPHA_TOKEN_ADDRESS as `0x${string}`, + ); + + const { + pendingRewards, + isLoading, + claim, + waitForClaimReceipt, + isClaiming, + updatePendingRewards, + } = usePendingRewards({ user: executor }); + + const [hasClaimed, setHasClaimed] = useState(false); + + const originalAsset = filteredAssets?.find( + (a) => a.address === HYPHA_TOKEN_ADDRESS, + ); + + const parsedRewardValue = + pendingRewards !== undefined ? Number(pendingRewards / 10n ** 18n) : 0; + + const baseHyphaAsset = originalAsset + ? { ...originalAsset, value: parsedRewardValue } + : { ...HYPHA_REWARDS_FALLBACK, value: parsedRewardValue }; + + const supplyFromAsset = (originalAsset as { supply?: { total: number } }) + ?.supply; + const supply = + supplyFromAsset ?? + (hyphaTotalSupply !== undefined ? { total: hyphaTotalSupply } : undefined); + + const hyphaTokenAsset = + pendingRewards !== undefined ? { ...baseHyphaAsset, supply } : undefined; + + useEffect(() => { + if (parsedRewardValue >= MIN_REWARD_CLAIM_VALUE) { + setHasClaimed(false); + } + }, [parsedRewardValue]); + + const updateSpaceAssets = useCallback(() => { + if (spaceSlug) { + mutate([`/api/v1/spaces/${spaceSlug}/assets`]); + } + }, [mutate, spaceSlug]); + + const disableClaimButton = + hasClaimed || + !(parsedRewardValue >= MIN_REWARD_CLAIM_VALUE) || + isClaiming || + pendingRewards === undefined; + + const canClaim = isAuthenticated && isMember; + + const onHandleClaim = useCallback(async () => { + if (!canClaim || !executor) return; + try { + const txHash = await claim(); + await waitForClaimReceipt(txHash as `0x${string}`); + await updatePendingRewards(); + await updateSpaceAssets(); + setHasClaimed(true); + } catch (error) { + console.error('Claim failed:', error); + } + }, [ + canClaim, + executor, + claim, + waitForClaimReceipt, + updatePendingRewards, + updateSpaceAssets, + ]); + + return ( +
+
+ + +
+
+ {isLoading || !executor ? ( +
+ +
+ ) : !isAuthenticated ? ( + +

Sign in to view space rewards

+
+ ) : ( +
+ +
+ )} +
+
+ ); +}; diff --git a/packages/epics/src/treasury/components/index.ts b/packages/epics/src/treasury/components/index.ts index fab042313..494acafe8 100644 --- a/packages/epics/src/treasury/components/index.ts +++ b/packages/epics/src/treasury/components/index.ts @@ -12,6 +12,7 @@ export * from './common/token-max-supply-field'; export * from './assets/user-assets-section'; export * from './requests/user-transactions-section'; export * from './assets/pending-rewards-section'; +export * from './assets/space-pending-rewards-section'; export * from './common/token-max-supply-type-field'; export * from './common/transfer-whitelist-field-array'; export * from './common/general-token-settings';