Skip to content

Commit

Permalink
Merge pull request #268 from Official-MoonDao/jade/de-prize-join
Browse files Browse the repository at this point in the history
Join DePrize
  • Loading branch information
jaderiverstokes authored Dec 17, 2024
2 parents 030e0da + 63807a5 commit cfdcbb1
Show file tree
Hide file tree
Showing 12 changed files with 404 additions and 34 deletions.
41 changes: 41 additions & 0 deletions ui/components/nance/CompetitorPreview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NFT, ThirdwebNftMedia } from '@thirdweb-dev/react'
import { useEffect, useState } from 'react'

type CompetitorPreviewProps = {
teamId: any
teamContract?: any
}

export function CompetitorPreview({
teamId,
teamContract,
}: CompetitorPreviewProps) {
const [teamNFT, setTeamNFT] = useState<NFT>()

useEffect(() => {
async function getTeamNFT() {
const nft = await teamContract.erc721.get(teamId)
setTeamNFT(nft)
}

if (teamContract?.erc721?.get && teamId) {
getTeamNFT()
}
}, [teamId, teamContract])

return (
<div className="flex items-center gap-5">
{teamNFT && (
<div className="flex items-center">
<ThirdwebNftMedia
metadata={teamNFT.metadata}
width="66px"
height="66px"
style={{ borderRadius: '50%' }}
/>
<div>{teamNFT.metadata.name}</div>
</div>
)}
</div>
)
}
146 changes: 146 additions & 0 deletions ui/components/nance/DePrize.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Arbitrum, Sepolia } from '@thirdweb-dev/chains'
import { useAddress, useContract } from '@thirdweb-dev/react'
import CompetitorABI from 'const/abis/Competitor.json'
import ERC20 from 'const/abis/ERC20.json'
import REVDeployer from 'const/abis/REVDeployer.json'
import {
DEPRIZE_ID,
COMPETITOR_TABLE_ADDRESSES,
REVNET_ADDRESSES,
} from 'const/config'
import { TEAM_ADDRESSES } from 'const/config'
import { useState, useContext } from 'react'
import toast from 'react-hot-toast'
import { useTeamWearer } from '@/lib/hats/useTeamWearer'
import toastStyle from '@/lib/marketplace/marketplace-utils/toastConfig'
import useWindowSize from '@/lib/team/use-window-size'
import ChainContext from '@/lib/thirdweb/chain-context'
import useTokenSupply from '@/lib/tokens/hooks/useTokenSupply'
import useWatchTokenBalance from '@/lib/tokens/hooks/useWatchTokenBalance'
import Asset from '@/components/dashboard/treasury/balance/Asset'
import Container from '@/components/layout/Container'
import ContentLayout from '@/components/layout/ContentLayout'
import Head from '@/components/layout/Head'
import { NoticeFooter } from '@/components/layout/NoticeFooter'
import { CompetitorPreview } from '@/components/nance/CompetitorPreview'
import { JoinDePrizeModal } from '@/components/nance/JoinDePrizeModal'
import StandardButton from '../layout/StandardButton'

export type Metadata = {}
export type Competitor = {
id: string
deprize: number
teamId: number
metadata: Metadata
}

export type DePrizeProps = {
competitors: Competitor[]
refreshRewards: () => void
}

export function DePrize({ competitors, refreshRewards }: DePrizeProps) {
const { selectedChain } = useContext(ChainContext)
const [joinModalOpen, setJoinModalOpen] = useState(false)
const userAddress = useAddress()

const { contract: competitorContract } = useContract(
COMPETITOR_TABLE_ADDRESSES[selectedChain.slug],
CompetitorABI
)
const { contract: teamContract } = useContract(
TEAM_ADDRESSES[selectedChain.slug]
)

const userTeams = useTeamWearer(teamContract, selectedChain, userAddress)

const isCompetitor = userTeams.some((team: any) =>
competitors.some(
(competitor) => competitor.teamId.toString() === team.teamId
)
)
const handleJoinWithTeam = async (teamId: string) => {
try {
await competitorContract?.call('insertIntoTable', [
DEPRIZE_ID,
teamId,
'{}',
])
toast.success('Joined as a competitor!', {
style: toastStyle,
})
setJoinModalOpen(false)
setTimeout(() => {
refreshRewards()
}, 5000)
} catch (error) {
console.error('Error joining as a competitor:', error)
toast.error('Error joining as a competitor. Please try again.', {
style: toastStyle,
})
}
}
return (
<section id="rewards-container" className="overflow-hidden">
<Head
title="DePrize"
description="Distribute rewards to contributors based on their contributions."
/>
<Container>
<ContentLayout
header={'DePrize'}
description="Distribute rewards to contributors based on their contributions."
headerSize="max(20px, 3vw)"
preFooter={<NoticeFooter />}
mainPadding
mode="compact"
popOverEffect={false}
isProfile
>
{!isCompetitor && (
<>
<StandardButton
onClick={() => setJoinModalOpen(true)}
className="gradient-2 rounded-full"
>
Join
</StandardButton>
{joinModalOpen && (
<JoinDePrizeModal
userTeams={userTeams}
setJoinModalOpen={setJoinModalOpen}
teamContract={teamContract}
handleJoinWithTeam={handleJoinWithTeam}
/>
)}
</>
)}
<div className="pb-32 w-full flex flex-col gap-4 py-2">
<div className="flex justify-between items-center">
<h3 className="title-text-colors text-2xl font-GoodTimes">
Competitors
</h3>
</div>
<div>
{competitors &&
competitors.length > 0 &&
competitors.map((competitor, i: number) => (
<div
key={i}
className="flex items-center w-full py-1 text-[17px]"
>
<div className="flex-1 px-8">
<CompetitorPreview
teamId={competitor.teamId}
teamContract={teamContract}
/>
</div>
</div>
))}
</div>
</div>
</ContentLayout>
</Container>
</section>
)
}
66 changes: 66 additions & 0 deletions ui/components/nance/JoinDePrizeModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { XMarkIcon } from '@heroicons/react/24/outline'
import Modal from '@/components/layout/Modal'
import { CompetitorPreview } from '@/components/nance/CompetitorPreview'
import StandardButton from '../layout/StandardButton'

type JoinDePrizeModalProps = {
userTeams: any[]
setJoinModalOpen: (open: boolean) => void
teamContract: any
handleJoinWithTeam: (teamId: string) => void
}

export function JoinDePrizeModal({
userTeams,
setJoinModalOpen,
teamContract,
handleJoinWithTeam,
}: JoinDePrizeModalProps) {
return (
<Modal id="join-deprize-modal" setEnabled={setJoinModalOpen}>
<button
id="close-modal"
type="button"
className="flex h-10 w-10 border-2 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
onClick={() => setJoinModalOpen(false)}
>
<XMarkIcon className="h-6 w-6 text-white" aria-hidden="true" />
</button>
<div className="p-6">
<h3 className="text-xl mb-4">Select a Team or Create a New One</h3>

{/* Existing Teams */}
{userTeams && userTeams.length > 0 && (
<div className="mb-6">
<h4 className="text-lg mb-2">Your Teams</h4>
<div className="space-y-2">
{userTeams.map((team: any) => (
<button
key={team.teamId}
onClick={() => handleJoinWithTeam(team.teamId)}
className="w-full p-3 text-left hover:bg-gray-100 rounded-lg transition-colors"
>
<CompetitorPreview
teamId={team.teamId}
teamContract={teamContract}
/>
</button>
))}
</div>
</div>
)}

{/* Create New Team */}
<div className="mt-4">
<StandardButton
className="gradient-2 rounded-full w-full"
hoverEffect={false}
link="/team"
>
Create New Team
</StandardButton>
</div>
</div>
</Modal>
)
}
1 change: 1 addition & 0 deletions ui/const/abis/Competitor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"type":"constructor","inputs":[{"name":"_table_prefix","type":"string","internalType":"string"}],"stateMutability":"nonpayable"},{"type":"function","name":"currId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getPolicy","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TablelandPolicy","components":[{"name":"allowInsert","type":"bool","internalType":"bool"},{"name":"allowUpdate","type":"bool","internalType":"bool"},{"name":"allowDelete","type":"bool","internalType":"bool"},{"name":"whereClause","type":"string","internalType":"string"},{"name":"withCheck","type":"string","internalType":"string"},{"name":"updatableColumns","type":"string[]","internalType":"string[]"}]}],"stateMutability":"payable"},{"type":"function","name":"getPolicy","inputs":[{"name":"caller","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"tuple","internalType":"struct TablelandPolicy","components":[{"name":"allowInsert","type":"bool","internalType":"bool"},{"name":"allowUpdate","type":"bool","internalType":"bool"},{"name":"allowDelete","type":"bool","internalType":"bool"},{"name":"whereClause","type":"string","internalType":"string"},{"name":"withCheck","type":"string","internalType":"string"},{"name":"updatableColumns","type":"string[]","internalType":"string[]"}]}],"stateMutability":"payable"},{"type":"function","name":"getTableId","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"getTableName","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"insertIntoTable","inputs":[{"name":"deprize","type":"uint256","internalType":"uint256"},{"name":"teamId","type":"uint256","internalType":"uint256"},{"name":"metadata","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"setAccessControl","inputs":[{"name":"controller","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"ChainNotSupported","inputs":[{"name":"chainid","type":"uint256","internalType":"uint256"}]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]}]
1 change: 1 addition & 0 deletions ui/const/abis/DePrizeDistribution.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"inputs":[{"internalType":"string","name":"_table_prefix","type":"string"},{"internalType":"address","name":"_prize_contract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"chainid","type":"uint256"}],"name":"ChainNotSupported","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"StringsInsufficientHexLength","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"_prizeContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTableId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTableName","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"deprize","type":"uint256"},{"internalType":"string","name":"distribution","type":"string"}],"name":"insertIntoTable","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"controller","type":"address"}],"name":"setAccessControl","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]
1 change: 1 addition & 0 deletions ui/const/abis/REVDeployer.json

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion ui/const/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ export const PROJECT_TABLE_ADDRESSES: Index = {
arbitrum: '0x4c2d6567D81A34117E894b6fDC97C9824f80D961',
'arbitrum-sepolia': '0xF0A3DB6161D1Ee7B99197CDeD4EdFc462EAE80e0',
}
export const COMPETITOR_TABLE_ADDRESSES: Index = {
sepolia: '0x9057Fff69e8b016a214C4f894430F71dad50b42c',
}
export const DISTRIBUTION_TABLE_ADDRESSES: Index = {
arbitrum: '0xabD8D3693439A72393220d87aee159952261Ad1f',
'arbitrum-sepolia': '0xd1D57F18252D06a6b28DE96B6cbF7F4283A4F205',
Expand All @@ -130,6 +133,12 @@ export const VOTING_ESCROW_DEPOSITOR_ADDRESSES: Index = {
sepolia: '0xe77ede9B472E9AE450a1AcD4A90dcd3fb2e50cD0',
}

export const REVNET_ADDRESSES: Index = {
sepolia: '0x25bc5d5a708c2e426ef3a5196cc18de6b2d5a3d1',
}

export const DEPRIZE_ID = 1

export const CITIZEN_WHITELIST_ADDRESSES: Index = {
arbitrum: '0xd594DBF360D666c94615Fb186AF3cB1018Be1616',
sepolia: '',
Expand Down Expand Up @@ -176,7 +185,7 @@ export const TEAM_DISCOUNTLIST_ADDRESSES: Index = {

export const MOONDAO_HAT_TREE_IDS: Index = {
arbitrum: '0x0000002a',
sepolia: '0x0000017c',
sepolia: '0x00000182',
}

export const JOBS_TABLE_ADDRESSES: Index = {
Expand Down
39 changes: 39 additions & 0 deletions ui/lib/tokens/hooks/useTokenBalances.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useEffect, useState } from 'react'

export default function useTokenBalances(
tokenContract: any,
decimals: number,
addresses: string[]
) {
const [tokenBalances, setTokenBalances] = useState<number[]>([])

useEffect(() => {
async function getBalances() {
if (!tokenContract || !addresses || addresses.length === 0) return

try {
const balances = await Promise.all(
addresses.map(async (address) => {
try {
const balance = await tokenContract.balanceOf(address) // Adjust this for your library
return +balance.toString() / 10 ** decimals
} catch (error) {
console.error(
`Failed to fetch balance for address ${address}:`,
error
)
return 0
}
})
)
setTokenBalances(balances)
} catch (error) {
console.error('Error fetching balances:', error)
}
}

getBalances()
}, [tokenContract, addresses, decimals])

return tokenBalances
}
16 changes: 16 additions & 0 deletions ui/lib/tokens/hooks/useTokenSupply.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useEffect, useState } from 'react'

export default function useTokenSupply(tokenContract: any, decimals: number) {
const [tokenSupply, setTokenSupply] = useState<number>(0)

useEffect(() => {
async function getSupply() {
if (!tokenContract) return
const supply = await tokenContract.call('totalSupply')
setTokenSupply(+supply.toString() / 10 ** decimals)
}

getSupply()
}, [tokenContract])
return tokenSupply
}
6 changes: 3 additions & 3 deletions ui/lib/utils/voting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,10 @@ export function runIterativeNormalization(distributions: any, projects: any) {
return return_tuple
}

function runQuadraticVoting(
export function runQuadraticVoting(
distributions: Distribution[],
addressToQuadraticVotingPower: any
addressToQuadraticVotingPower: any,
budgetPercentMinusCommunityFund = 90
) {
const projectIdToEstimatedPercentage: { [key: string]: number } = {}
const projectIdToListOfPercentage: { [key: string]: number[] } = {}
Expand All @@ -202,7 +203,6 @@ function runQuadraticVoting(
)
) / votingPowerSum
}
const budgetPercentMinusCommunityFund = 90
// normalize projectIdToEstimatedPercentage
const sum = _.sum(Object.values(projectIdToEstimatedPercentage))
for (const [projectId, percentage] of Object.entries(
Expand Down
Loading

0 comments on commit cfdcbb1

Please sign in to comment.