Skip to content

Commit 61aae93

Browse files
committed
Implement payouts page
1 parent 8149f77 commit 61aae93

File tree

8 files changed

+237
-37
lines changed

8 files changed

+237
-37
lines changed

Diff for: src/hooks/useBlockTime.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export function useBlockTime(blocks: number | BN = BN_ONE, apiOverride?: ApiProm
2424
(a.consts.timestamp?.minimumPeriod.gte(THRESHOLD)
2525
? a.consts.timestamp.minimumPeriod.mul(BN_TWO)
2626
: a.query.parachainSystem
27-
? DEFAULT_TIME.mul(BN_TWO)
28-
: DEFAULT_TIME)
27+
? DEFAULT_TIME.mul(BN_TWO)
28+
: DEFAULT_TIME)
2929
const value = blockTime.mul(bnToBn(blocks)).toNumber()
3030
const time = extractTime(Math.abs(value))
3131
const { days, hours, minutes, seconds } = time

Diff for: src/pages/explore/ExplorePage.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CandidatesPage } from './CandidatesPage'
66
import { LoadingSpinner } from './components/LoadingSpinner'
77
import { NavigationBar } from './components/NavigationBar'
88
import { MembersPage } from './MembersPage'
9+
import { PayoutsPage } from './PayoutsPage'
910
import { ProofOfInkPage } from './ProofOfInkPage'
1011
import { SuspendedPage } from './SuspendedPage'
1112
import { NavigateWithQuery } from '../../components/NavigateWithQuery'
@@ -79,6 +80,7 @@ const ExplorePage = (): JSX.Element => {
7980
<Route path="/bidders" element={<BiddersPage api={api} />} />
8081
<Route path="/candidates" element={<CandidatesPage api={api} handleUpdateTotal={handleUpdateTotal} />} />
8182
<Route path="/members" element={<MembersPage api={api} />} />
83+
<Route path="/payouts" element={<PayoutsPage api={api} />} />
8284
<Route path="/suspended" element={<SuspendedPage api={api} />} />
8385
<Route path="/poi/*" element={<ProofOfInkPage api={api} />} />
8486
</Routes>

Diff for: src/pages/explore/MembersPage/components/PayoutsAccordion.tsx

-35
This file was deleted.
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { ApiPromise } from '@polkadot/api'
2+
import { WalletAccount } from '@talismn/connect-wallets'
3+
import { Badge, Col } from 'react-bootstrap'
4+
import styled from 'styled-components'
5+
import { DataHeaderRow, DataRow } from '../../../../components/base'
6+
import { FormatBalance } from '../../../../components/FormatBalance'
7+
import { Identicon } from '../../components/Identicon'
8+
9+
const StyledDataRow = styled(DataRow)`
10+
background-color: ${(props) => (props.$isDefender ? props.theme.colors.black : '')};
11+
border: ${(props) => (props.$isDefender ? `2px solid ${props.theme.colors.secondary}` : '')};
12+
&:hover {
13+
cursor: pointer;
14+
}
15+
@media (max-width: 992px) {
16+
padding-block: 12px;
17+
margin-inline: 2px;
18+
}
19+
`
20+
21+
type PayoutsListProps = {
22+
api: ApiPromise
23+
members: ExtendedSocietyMember[]
24+
activeAccount: WalletAccount | undefined
25+
handleUpdate: () => void
26+
}
27+
28+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
29+
const PayoutsList = ({ api, members }: PayoutsListProps): JSX.Element => {
30+
// Likely impossible to happen but if it does, better to show a
31+
// clear message than an empty list which may look like a loading state
32+
if (members.length === 0) return <>No members</>
33+
34+
return (
35+
<>
36+
<DataHeaderRow className="d-none d-lg-flex text-center">
37+
<Col lg={1}>#</Col>
38+
<Col lg={5} className="text-center text-lg-start">
39+
Wallet Hash
40+
</Col>
41+
<Col lg={2} className="text-center text-lg-start">
42+
Paid
43+
</Col>
44+
<Col lg={2} className="text-center text-lg-start">
45+
Pending
46+
</Col>
47+
<Col lg={2}></Col>
48+
</DataHeaderRow>
49+
50+
{members.map((member: ExtendedSocietyMember) => (
51+
<StyledDataRow key={member.accountId.toString()}>
52+
<Col lg={1} className="text-center">
53+
<Identicon value={member.accountId.toHuman()} size={32} theme={'polkadot'} />
54+
</Col>
55+
<Col lg={5} className="text-center text-lg-start">
56+
{member.accountId.toHuman()}
57+
</Col>
58+
<Col lg={2} className="text-center text-lg-start">
59+
<FormatBalance balance={member.extendedPayouts.paid} />
60+
</Col>
61+
<Col lg={2} className="text-center text-lg-start">
62+
<FormatBalance balance={member.extendedPayouts.pending} />
63+
</Col>
64+
<Col lg={2} className="text-center text-lg-end">
65+
{member.isFounder && (
66+
<Badge pill bg="dark" className="me-2 p-2">
67+
Founder
68+
</Badge>
69+
)}
70+
{member.rank.toNumber() > 0 && (
71+
<Badge pill bg="dark" className="me-2 p-2">
72+
Ranked
73+
</Badge>
74+
)}
75+
{member.hasPayouts && (
76+
<Badge pill bg="primary" className="me-2 p-2">
77+
Maturing in block {member.extendedPayouts.block}
78+
</Badge>
79+
)}
80+
{member.extendedPayouts.pending == 0 && member.extendedPayouts.paid > 0 && (
81+
<Badge pill bg="secondary" text="black" className="me-2 p-2">
82+
Paid
83+
</Badge>
84+
)}
85+
{!member.hasPayouts && member.extendedPayouts.paid == 0 && (
86+
<Badge pill bg="black" className="me-2 p-2">
87+
V1
88+
</Badge>
89+
)}
90+
</Col>
91+
</StyledDataRow>
92+
))}
93+
</>
94+
)
95+
}
96+
97+
export { PayoutsList }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { ApiPromise } from '@polkadot/api'
2+
// import { Data } from '@polkadot/types'
3+
import { AccountId } from '@polkadot/types/interfaces'
4+
import '@polkadot/api-augment/substrate'
5+
// import { PalletIdentityLegacyIdentityInfo } from '@polkadot/types/lookup'
6+
// import { u8aToBuffer } from '@polkadot/util'
7+
8+
export async function fetchMemberDetails(api: ApiPromise, accountId: AccountId): Promise<SocietyMemberDetails> {
9+
const [accountInfo, maybeIdentity] = await Promise.all([
10+
api.derive.accounts.info(accountId),
11+
api.query.identity.identityOf(accountId)
12+
])
13+
14+
const identity = maybeIdentity.isSome ? undefined : undefined // temporary until we integrate people's chain
15+
16+
const rawIndex = accountInfo?.accountIndex
17+
const index = rawIndex ? api.registry.createType('AccountIndex', rawIndex.toNumber()).toString() : undefined
18+
19+
return { accountId, identity, index }
20+
}
21+
22+
// function buildAccountIdentity(identityInfo: PalletIdentityLegacyIdentityInfo): AccountIdentity {
23+
// return {
24+
// name: decode(identityInfo.display) ?? '(Unable to get name)',
25+
// email: decode(identityInfo.email),
26+
// legal: decode(identityInfo.legal),
27+
// riot: decode(identityInfo.riot),
28+
// twitter: decode(identityInfo.twitter),
29+
// webpage: decode(identityInfo.web)
30+
// }
31+
// }
32+
33+
// const decode = (data: Data) => (data.isEmpty ? undefined : u8aToBuffer(data.toU8a()).toString())

Diff for: src/pages/explore/PayoutsPage/index.tsx

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { ApiPromise } from '@polkadot/api'
2+
import { Vec } from '@polkadot/types'
3+
import { AccountId32 } from '@polkadot/types/interfaces'
4+
import { PalletSocietyPayoutRecord } from '@polkadot/types/lookup'
5+
import { useEffect, useState } from 'react'
6+
import { PayoutsList } from './components/PayoutsList'
7+
import { useAccount } from '../../../account/AccountContext'
8+
import { useConsts } from '../../../hooks/useConsts'
9+
import { LoadingSpinner } from '../components/LoadingSpinner'
10+
import { buildSocietyMembersArray, deriveMembersInfo } from '../helpers'
11+
12+
type PayoutsPageProps = {
13+
api: ApiPromise | null
14+
}
15+
16+
const PayoutsPage = ({ api }: PayoutsPageProps): JSX.Element => {
17+
const { activeAccount } = useAccount()
18+
const [members, setMembers] = useState<ExtendedSocietyMember[] | null>(null)
19+
const [trigger, setTrigger] = useState(false)
20+
const society = api?.derive.society
21+
22+
const { graceStrikes } = useConsts()
23+
24+
const handleUpdate = () => {
25+
setTrigger((prev) => !prev)
26+
}
27+
28+
let defender: AccountId32
29+
let skeptic: AccountId32
30+
31+
useEffect(() => {
32+
const fetchData = async () => {
33+
if (!api || !society) return
34+
35+
const info: ExtendedDeriveSociety = await society.info()
36+
const defending = await api.query.society.defending()
37+
38+
defender = defending.unwrap()[0]
39+
skeptic = defending.unwrap()[1]
40+
41+
info.defender = defender
42+
info.skeptic = skeptic
43+
44+
const responseMembers: ExtendedDeriveSociety[] = await deriveMembersInfo(api)
45+
const membersArray = buildSocietyMembersArray(responseMembers, info, graceStrikes)
46+
47+
const extendedMembers: ExtendedSocietyMember[] = await Promise.all(
48+
membersArray.map(async (member) => {
49+
const payoutsCodec = (await api.query.society.payouts(member.accountId)) as PalletSocietyPayoutRecord
50+
51+
const payouts = payoutsCodec.payouts.toArray()
52+
const highestBlockNumber = payouts.length > 0 ? Math.max(...payouts.map((payout) => payout[0].toNumber())) : 0
53+
const totalBalance = payouts.reduce((sum: number, payout) => sum + payout[1].toBn().toNumber(), 0)
54+
55+
return {
56+
...member,
57+
extendedPayouts: {
58+
block: highestBlockNumber,
59+
pending: totalBalance,
60+
paid: payoutsCodec.paid
61+
}
62+
}
63+
})
64+
)
65+
66+
const sortedMembers = extendedMembers.sort((a, b) => {
67+
const blockA = a.extendedPayouts.block === 0 ? Number.MAX_SAFE_INTEGER : a.extendedPayouts.block
68+
const blockB = b.extendedPayouts.block === 0 ? Number.MAX_SAFE_INTEGER : b.extendedPayouts.block
69+
70+
const blockComparison = blockA - blockB
71+
if (blockComparison !== 0) {
72+
return blockComparison
73+
}
74+
75+
const pendingComparison = b.extendedPayouts.pending - a.extendedPayouts.pending
76+
if (pendingComparison !== 0) {
77+
return pendingComparison
78+
}
79+
80+
return b.extendedPayouts.paid.toBn().cmp(a.extendedPayouts.paid.toBn())
81+
})
82+
83+
setMembers(sortedMembers)
84+
}
85+
86+
fetchData()
87+
}, [trigger, society, api, graceStrikes])
88+
89+
if (members === null) return <LoadingSpinner />
90+
91+
return <PayoutsList api={api!} members={members} activeAccount={activeAccount} handleUpdate={handleUpdate} />
92+
}
93+
94+
export { PayoutsPage }

Diff for: src/pages/explore/components/NavigationBar.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ const NavigationBar = ({ totals }: { totals: Totals }) => {
5656
Journey
5757
</Nav.Link>
5858
</StyledNavItem>
59+
<StyledNavItem>
60+
<Nav.Link as={LinkWithQuery} to="/explore/payouts">
61+
Payouts
62+
</Nav.Link>
63+
</StyledNavItem>
5964
</StyledNav>
6065
{showSubNav && (
6166
<StyledNav defaultActiveKey="/explore/poi/examples" className="py-2 mb-4">

Diff for: src/types/global.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ type ExtendedDeriveSociety = {
5454
rank: u32
5555
} & DeriveSociety
5656

57+
interface ExtendedSocietyMember extends SocietyMember {
58+
extendedPayouts: any
59+
}
60+
5761
interface SocietyMemberDetails {
5862
accountId: AccountId
5963
index?: string

0 commit comments

Comments
 (0)