Skip to content

Commit

Permalink
Merge pull request #91 from YoubetDao/feat/period
Browse files Browse the repository at this point in the history
Feat/period
  • Loading branch information
wfnuser authored Dec 13, 2024
2 parents 980caf2 + 183e1c7 commit 350485f
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 174 deletions.
9 changes: 8 additions & 1 deletion src/constants/distributor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export class Distributor {
'function approve(address spender, uint256 amount) external returns (bool)',
'function allowance(address owner, address spender) external view returns (uint256)',
'function balanceOf(address account) external view returns (uint256)',
'function symbol() external view returns (string)',
'function decimals() external view returns (uint8)',
]

constructor(distributorAddress: string) {
Expand Down Expand Up @@ -52,6 +54,11 @@ export class Distributor {
return await tx.wait()
}

async getTokenSymbolAndDecimals() {
const tokenContract = await this.getTokenContract()
return [await tokenContract.symbol(), await tokenContract.decimals()]
}

async getAllowance(walletAddress: string) {
const tokenContract = await this.getTokenContract()
return await tokenContract.allowance(walletAddress, await this.getAddress())
Expand Down Expand Up @@ -81,7 +88,7 @@ export class Distributor {
}
}

const distributorAddress = import.meta.env.VITE_DISTRIBUTOR_ADDRESS || '0x1a48F5d414DDC79a79f519A665e03692B2a2c450'
const distributorAddress = import.meta.env.VITE_DISTRIBUTOR_ADDRESS || '0xBE639b42A3818875D59992d80F18280387cFB412'

const distributor = new Distributor(distributorAddress)

Expand Down
44 changes: 44 additions & 0 deletions src/hooks/useDistributorToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { distributor } from '@/constants/distributor'

import { useEffect, useState } from 'react'

interface TokenInfo {
symbol: string
decimals: number
loading: boolean
error: Error | null
}

export function useDistributorToken() {
const [tokenInfo, setTokenInfo] = useState<TokenInfo>({
symbol: 'USDT',
decimals: 6,
loading: true,
error: null,
})

useEffect(() => {
const getTokenInfo = async () => {
try {
const [symbol, decimals] = await distributor.getTokenSymbolAndDecimals()
setTokenInfo({
symbol,
decimals,
loading: false,
error: null,
})
} catch (error) {
console.log(error)
setTokenInfo((prev) => ({
...prev,
loading: false,
error: error instanceof Error ? error : new Error('Unknown error'),
}))
}
}

getTokenInfo()
}, [])

return tokenInfo
}
11 changes: 11 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,14 @@ export const getRandomColor = (colors: string[]) => {
export const capitalize = (str: string) => {
return _.capitalize(str)
}

export const formatDate = (date?: string) => {
if (!date) return ''
return new Date(date).toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
})
}
207 changes: 55 additions & 152 deletions src/pages/myrewards/index.tsx
Original file line number Diff line number Diff line change
@@ -1,198 +1,101 @@
import React, { useEffect, useState } from 'react'
import { fetchPeriod, getLoadMoreProjectList } from '@/service'
import { IResultPagination, IResultPaginationData, Period, Project } from '@/types'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import React, { useState } from 'react'
import { claimReceipt, fetchReceipts, getRewardSignature } from '@/service'
import { IResultPaginationData, Receipt, ReceiptStatus } from '@/types'
import { LoadingCards } from '@/components/loading-cards'
import { useAccount } from 'wagmi'
import { useInfiniteScroll } from 'ahooks'
import { DEFAULT_PAGINATION_LIMIT } from '@/constants/data'
import PaginationFast from '@/components/pagination-fast'
import { useQuery } from '@tanstack/react-query'
import { useSearchParams } from 'react-router-dom'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Button } from '@/components/ui/button'
import { distributor } from '@/constants/distributor'
import { toast } from '@/components/ui/use-toast'
import { useAtom } from 'jotai'
import { usernameAtom } from '@/store'

interface ProjectListProps {
loading: boolean
loadingMore: boolean
data: IResultPagination<Project> | undefined
}

function ProjectList({ loading, loadingMore, data }: ProjectListProps) {
if (loading) return <LoadingCards />
if (!data) return null

return (
<div className="flex w-full flex-col overflow-hidden p-2">
<div className="flex items-center justify-between">
<div className="text-sm text-muted-foreground">{data.pagination.totalCount} Projects</div>
</div>
<div className="flex w-full flex-col gap-2">
{data.list.map((project) => (
<SelectItem key={project._id} value={project._id.toString()}>
{project.name}
</SelectItem>
))}
{loadingMore && <LoadingCards count={1} />}
</div>
</div>
)
}
import { formatDate } from '@/lib/utils'

function RewardsTable(): React.ReactElement {
const [page, setPage] = useState(1)
const [github] = useAtom(usernameAtom)
const [urlParam] = useSearchParams('')
const [projectId, setProjectId] = useState<string | undefined>(undefined)
const [filterTags] = useState<string[]>([])
const { address, chain } = useAccount()
const pageSize = 10
const queryClient = useQueryClient()

const {
data: projects,
loading: projectLoading,
loadingMore: projectLoadingMore,
reload,
} = useInfiniteScroll<IResultPagination<Project>>(
async (d) => {
const res = await getLoadMoreProjectList({
offset: d ? d.pagination.currentPage * DEFAULT_PAGINATION_LIMIT : 0,
limit: DEFAULT_PAGINATION_LIMIT,
filterTags,
search: decodeURIComponent(urlParam.get('search') || ''),
sort: decodeURIComponent(urlParam.get('sort') || ''),
onlyPeriodicReward: true,
})
if (!projectId) setProjectId(res.list[0]._id.toString())
return res
},
{
manual: true,
target: document.querySelector('#scrollRef'),
isNoMore: (data) => {
return data ? !data.pagination.hasNextPage : false
},
},
)

const { data: periods, isLoading: isPullRequestsLoading } = useQuery<IResultPaginationData<Period> | undefined>({
queryKey: ['periods', projectId ?? ''],
const { data: periods, isLoading: isPullRequestsLoading } = useQuery<IResultPaginationData<Receipt> | undefined>({
queryKey: ['receipts'],
queryFn: () => {
return fetchPeriod({
return fetchReceipts({
offset: (page - 1) * pageSize,
limit: pageSize,
projectId: projectId ?? '',
})
},
})

const totalPages = Math.ceil((periods?.pagination.totalCount || 0) / pageSize)

useEffect(() => {
reload()
}, [filterTags, reload, urlParam])

return (
<div className="space-y-4">
<Select value={projectId} onValueChange={setProjectId}>
<SelectTrigger className="w-[180px] border-gray-700 bg-transparent">
<SelectValue placeholder="Select project" />
</SelectTrigger>
<SelectContent>
{!projectLoading && projects ? (
<ProjectList loading={projectLoading} loadingMore={projectLoadingMore} data={projects} />
) : (
<LoadingCards count={1} />
)}
</SelectContent>
</Select>

{!isPullRequestsLoading && periods ? (
<Table>
<TableHeader>
<TableRow>
<TableHead className="text-gray-400">From</TableHead>
<TableHead className="text-gray-400">To</TableHead>
<TableHead className="text-gray-400">Users</TableHead>
<TableHead className="text-gray-400">Pr count</TableHead>
<TableHead className="text-gray-400">Period</TableHead>
<TableHead className="text-gray-400">Amount</TableHead>
<TableHead className="text-gray-400">Reward</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{periods.data.map((periodPrs) => {
{periods.data.map((receipts) => {
return (
<TableRow key={periodPrs._id}>
<TableCell className="font-medium">
{new Date(periodPrs.from).toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
})}
</TableCell>
<TableRow key={receipts._id}>
<TableCell>
{new Date(periodPrs.to).toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
hour: 'numeric',
minute: 'numeric',
})}
{formatDate(receipts.source.period?.from)} - {formatDate(receipts.source.period?.to)}
</TableCell>
<TableCell>
<div className="flex -space-x-3">
{periodPrs.contributors.map((user) => {
return (
<img
key={user._id}
className="h-6 w-6 rounded-full border-2 border-white"
src={user.avatarUrl}
alt={user._id}
/>
)
})}
</div>
{receipts.status == ReceiptStatus.CLAIMED
? `${receipts.detail.amount} ${receipts.detail.symbol}`
: '***'}
</TableCell>
<TableCell>{periodPrs.pullRequests.length}</TableCell>
<TableCell>
{!periodPrs.rewardGranted ? (
address &&
github &&
chain && (
<Button
variant="link"
className="gap-2 p-0 text-blue-500"
onClick={async () => {
try {
// TODO: fetch signature from backend
const signature =
'0x931d5b9cbc5d00d5dd42352827fa5423ec932ef45eb3afad3f2f8173fabe588451e97cdd849f76d728047a48e4f0a1064287584f155569b2339bc9e16ce6acfb1c'

await distributor.claimRedPacket(periodPrs._id, github, signature)

toast({
title: 'Success',
description: 'Reward claimed successfully',
})
} catch (error) {
toast({
variant: 'destructive',
title: 'Error',
description: error instanceof Error ? error.message : 'Failed to claim reward',
})
}
}}
>
Claim
</Button>
{receipts.source.period ? (
receipts.status === ReceiptStatus.GRANTED ? (
address && github && chain ? (
<Button
variant="link"
className="gap-2 p-0 text-blue-500"
onClick={async () => {
if (!receipts.source.period) return
try {
const signature = await getRewardSignature(receipts.source.period._id)
await distributor.claimRedPacket(
receipts.source.period._id,
github,
signature.signature,
)
await claimReceipt(receipts._id)
queryClient.invalidateQueries({ queryKey: ['receipts'] })
toast({
title: 'Success',
description: 'Reward claimed successfully',
})
} catch (error) {
toast({
variant: 'destructive',
title: 'Error',
description: error instanceof Error ? error.message : 'Failed to claim reward',
})
}
}}
>
Claim
</Button>
) : (
<p>Please connect wallet</p>
)
) : (
<p>Claimed</p>
)
) : (
<p>Granted</p>
<p>Not Supported</p>
)}
</TableCell>
</TableRow>
Expand Down
10 changes: 8 additions & 2 deletions src/pages/period/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { fetchPeriod, getLoadMoreProjectList } from '@/service'
import { IResultPagination, IResultPaginationData, Project, Period } from '@/types'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { LoadingCards } from '@/components/loading-cards'
import { useAccount } from 'wagmi'
import { useAccount, useSwitchChain } from 'wagmi'
import { useInfiniteScroll } from 'ahooks'
import { DEFAULT_PAGINATION_LIMIT } from '@/constants/data'
import { DEFAULT_PAGINATION_LIMIT, paymentChain } from '@/constants/data'
import PaginationFast from '@/components/pagination-fast'
import { useQuery } from '@tanstack/react-query'
import { useSearchParams } from 'react-router-dom'
Expand Down Expand Up @@ -45,6 +45,7 @@ function ProjectList({ loading, loadingMore, data }: ProjectListProps) {

function PeriodTable(): React.ReactElement {
const [page, setPage] = useState(1)
const { switchChain } = useSwitchChain()
const [urlParam] = useSearchParams('')
const [projectId, setProjectId] = useState<string | undefined>('66cd6e1bdcdcc63c6a64bec3')
const [filterTags] = useState<string[]>([])
Expand Down Expand Up @@ -89,6 +90,10 @@ function PeriodTable(): React.ReactElement {
},
})

useEffect(() => {
switchChain({ chainId: paymentChain.id })
}, [switchChain])

const totalPages = Math.ceil((periods?.pagination.totalCount || 0) / pageSize)

const [hasAllowance, setHasAllowance] = useState(false)
Expand Down Expand Up @@ -195,6 +200,7 @@ function PeriodTable(): React.ReactElement {
variant="link"
className="gap-2 p-0 text-blue-500"
onClick={async () => {
await switchChain({ chainId: paymentChain.id })
await distributor.approveAllowance(ethers.parseEther('5000'))
await checkAllowance()
}}
Expand Down
Loading

0 comments on commit 350485f

Please sign in to comment.