Skip to content

Commit

Permalink
Merge pull request #49 from TalismanSociety/feat/voting-updates-react…
Browse files Browse the repository at this point in the history
…-query

[Feat] - Display referendum Titles, add react-query
  • Loading branch information
UrbanWill authored May 22, 2024
2 parents 0e980e8 + dfb2c79 commit 88ad428
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 105 deletions.
3 changes: 3 additions & 0 deletions apps/multisig/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
"@talismn/siws": "0.0.8",
"@talismn/ui": "workspace:^",
"@talismn/util": "^0.2.0",
"@tanstack/react-query": "^5.37.1",
"@tanstack/react-query-devtools": "^5.37.1",
"@tanstack/react-table": "^8.16.0",
"@uiw/codemirror-themes": "^4.21.13",
"@uiw/react-codemirror": "^4.21.13",
Expand Down Expand Up @@ -146,6 +148,7 @@
"@storybook/react": "^6.5.16",
"@storybook/testing-library": "^0.0.13",
"@talismn/development": "workspace:^",
"@tanstack/eslint-plugin-query": "^5.35.6",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
Expand Down
85 changes: 47 additions & 38 deletions apps/multisig/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,43 +32,52 @@ import { SkeletonLayout } from './layouts/SkeletonLayout'
import { Helmet } from 'react-helmet'
import { CONFIG } from '@lib/config'

const App: React.FC = () => (
<ThemeProvider>
<RecoilRoot>
<BalancesProvider
withTestnets
enabledChains={supportedChains.map(chain => chain.genesisHash)}
coingeckoApiUrl="https://coingecko.talismn.workers.dev"
>
<HasuraProvider>
<AzeroIDResolverProvider>
<Suspense fallback={<SkeletonLayout />}>
<WalletConnectProvider>
<Helmet>
<title>{CONFIG.IS_POLKADOT_MULTISIG ? 'Polkadot Multisig by Signet' : 'Signet'}</title>
</Helmet>
<Analytics />
{/* <PendingTransactionsWatcher /> */}
<BalancesWatcher />
<ExtensionWatcher />
<AccountWatcher />
<OffchainDataWatcher />
<NomPoolsWatcher />
<ValidatorsWatcher />
<ActiveMultisigWatcher />
<ConstsWatcher />
<RouterProvider router={router} />
<Toaster position="top-right" containerStyle={{ top: '6.4rem' }}>
{t => <ToastBar toast={t} />}
</Toaster>
<NewToaster />
</WalletConnectProvider>
</Suspense>
</AzeroIDResolverProvider>
</HasuraProvider>
</BalancesProvider>
</RecoilRoot>
</ThemeProvider>
)
import { useQuery, useMutation, useQueryClient, QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const App: React.FC = () => {
const queryClient = new QueryClient()
return (
<ThemeProvider>
<RecoilRoot>
<BalancesProvider
withTestnets
enabledChains={supportedChains.map(chain => chain.genesisHash)}
coingeckoApiUrl="https://coingecko.talismn.workers.dev"
>
<HasuraProvider>
<AzeroIDResolverProvider>
<Suspense fallback={<SkeletonLayout />}>
<WalletConnectProvider>
<Helmet>
<title>{CONFIG.IS_POLKADOT_MULTISIG ? 'Polkadot Multisig by Signet' : 'Signet'}</title>
</Helmet>
<Analytics />
{/* <PendingTransactionsWatcher /> */}
<BalancesWatcher />
<ExtensionWatcher />
<AccountWatcher />
<OffchainDataWatcher />
<NomPoolsWatcher />
<ValidatorsWatcher />
<ActiveMultisigWatcher />
<ConstsWatcher />
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
<Toaster position="top-right" containerStyle={{ top: '6.4rem' }}>
{t => <ToastBar toast={t} />}
</Toaster>
<NewToaster />
</WalletConnectProvider>
</Suspense>
</AzeroIDResolverProvider>
</HasuraProvider>
</BalancesProvider>
</RecoilRoot>
</ThemeProvider>
)
}

export default App
51 changes: 51 additions & 0 deletions apps/multisig/src/hooks/queries/useGetReferendums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useQueries } from '@tanstack/react-query'
import { SupportedChainIds } from '@domains/chains/generated-chains'

const supportedChains: Partial<Record<SupportedChainIds, string>> = {
'polkadot': 'polkadot',
'kusama': 'kusama',
'acala': 'acala',
'bifrost-polkadot': 'bifrost',
'bifrost-kusama': 'bifrost-kusama',
'hydradx': 'hydradx',
'phala': 'phala',
'khala': 'khala',
'karura': 'karura',
'kintsugi': 'kintsugi',
// testnets
'rococo-testnet': 'rococo',
}
// This only a partial Referendum interface
interface Referendum {
title: string
referendumIndex: number
}

const fetchReferendums = async ({ chain, id }: { chain: string | undefined; id: string }): Promise<Referendum> => {
const data = await fetch(`https://${chain}.subsquare.io/api/gov2/referendums/${id}`).then(res => res.json())
return data
}

interface UseGetReferendums {
ids: string[]
chainId: SupportedChainIds
}

export default function useGetReferendums({ ids, chainId }: UseGetReferendums) {
const chain = supportedChains[chainId]

return useQueries({
queries: ids.map(id => ({
queryKey: [chainId, { isChainSupported: !!chain }, id],
queryFn: () => fetchReferendums({ chain, id }),
enabled: !!id && !!chain,
})),
combine: results => {
return {
data: results.map(result => result.data),
pending: results.some(result => result.isPending),
isLoading: results.some(result => result.isLoading),
}
},
})
}
119 changes: 71 additions & 48 deletions apps/multisig/src/layouts/NewTransaction/Vote/PendingVotes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useMemo } from 'react'
import { useReferenda } from '@domains/referenda'
import { useConfirmedTransactions } from '@domains/tx-history'
import { useRecoilValue } from 'recoil'
import useGetReferendums from '@hooks/queries/useGetReferendums'
import { selectedTeamsState } from '@domains/offchain-data'
import { Button } from '@talismn/ui'
import PendingVotesTable from './PendingVotesTable'
Expand All @@ -11,18 +12,28 @@ import { Multisig } from '@domains/multisig'
import { Transaction } from '@domains/multisig'
import { ColumnDef } from '@tanstack/react-table'
import BN from 'bn.js'
import { SupportedChainIds } from '@domains/chains/generated-chains'

interface PendingVotesProps {
multisig: Multisig
handleOnRemoveVote: (referendumId: string) => void
}

const PendingVotes: React.FC<PendingVotesProps> = ({ multisig, handleOnRemoveVote }) => {
const [latestTxs, setLatestTxs] = useState<Transaction[]>([])
const [referendumTxs, setReferendumTxs] = useState<Transaction[]>([])
const selectedTeams = useRecoilValue(selectedTeamsState)
const { transactions, loading: isTransactionsLoading } = useConfirmedTransactions(selectedTeams ?? [])
const { referendums, isLoading: isReferendumsLoading } = useReferenda(multisig.chain)

const txReferendumIds = useMemo(
() => referendumTxs.map(tx => String(tx.decoded!.voteDetails!.referendumId)),
[referendumTxs]
)
const { data: referendumsData, isLoading: isReferendumsDataLoading } = useGetReferendums({
chainId: multisig.chain.squidIds.chainData as SupportedChainIds,
ids: txReferendumIds,
})

const ongoingReferendumsIds = useMemo(
() => referendums?.flatMap(referendum => (referendum.isOngoing ? [referendum.index] : [])),
[referendums]
Expand Down Expand Up @@ -52,69 +63,81 @@ const PendingVotes: React.FC<PendingVotesProps> = ({ multisig, handleOnRemoveVot

useEffect(() => {
if (transactions?.length) {
setLatestTxs(filterLatestTransactions(transactions))
setReferendumTxs(filterLatestTransactions(transactions))
}
}, [filterLatestTransactions, transactions])

const columns: ColumnDef<Transaction>[] = [
{
header: 'Proposal',
accessorKey: 'description',
},
{
id: 'voteFor',
cell: ({ row: { original } }) => {
return (
<div className="flex items-center">
<VotePill voteDetails={original.decoded?.voteDetails!} />
</div>
)
const columns: ColumnDef<Transaction>[] = useMemo(
() => [
{
header: 'Proposal',
accessorKey: 'description',
cell: ({ row: { original } }) => {
const referendumId = original.decoded?.voteDetails?.referendumId
const { title } = referendumsData?.find(ref => ref?.referendumIndex === referendumId) || {}
const headlineText = title ? `Proposal #${referendumId} - ${title}` : `Proposal #${referendumId}`
return <div>{headlineText}</div>
},
},
},
{
id: 'amount',
cell: ({ row: { original } }) => {
const { convictionVote, details, token, method } = original.decoded?.voteDetails!
const { Standard, SplitAbstain } = details
{
id: 'voteFor',
cell: ({ row: { original } }) => {
return (
<div className="flex items-center">
<VotePill voteDetails={original.decoded?.voteDetails!} />
</div>
)
},
},
{
id: 'amount',
cell: ({ row: { original } }) => {
const { convictionVote, details, token, method } = original.decoded?.voteDetails!
const { Standard, SplitAbstain } = details

if (!method) return null
if (!method) return null

const amount =
convictionVote === 'SplitAbstain' && SplitAbstain
? Object.values(SplitAbstain).reduce((acc, balance) => acc.add(balance), new BN(0))
: new BN(0)
const amount =
convictionVote === 'SplitAbstain' && SplitAbstain
? Object.values(SplitAbstain).reduce((acc, balance) => acc.add(balance), new BN(0))
: new BN(0)

return <AmountRow balance={{ amount: Standard?.balance! || amount, token }} />
return <AmountRow balance={{ amount: Standard?.balance! || amount, token }} />
},
},
},
{
id: 'conviction',
cell: ({ row: { original } }) => {
if (original.decoded?.voteDetails?.convictionVote !== 'Standard') return null
return <div>{original.decoded?.voteDetails?.details.Standard?.vote.conviction}x</div>
{
id: 'conviction',
cell: ({ row: { original } }) => {
if (original.decoded?.voteDetails?.convictionVote !== 'Standard') return null
return <div>{original.decoded?.voteDetails?.details.Standard?.vote.conviction}x</div>
},
},
},
{
id: 'actions',
cell: ({ row: { original } }) => {
return (
<div className="flex justify-end">
<Button onClick={() => handleOnRemoveVote(String(original.decoded?.voteDetails?.referendumId))}>
Remove
</Button>
</div>
)
{
id: 'actions',
cell: ({ row: { original } }) => {
return (
<div className="flex justify-end">
<Button onClick={() => handleOnRemoveVote(String(original.decoded?.voteDetails?.referendumId))}>
Remove
</Button>
</div>
)
},
},
},
]
],
[handleOnRemoveVote, referendumsData]
)

return (
<div className="flex flex-col gap-8">
<h2>Pending votes</h2>
<PendingVotesTable
columns={columns}
data={latestTxs}
isLoading={(isTransactionsLoading || isReferendumsLoading) && latestTxs.length === 0}
data={referendumTxs}
isLoading={
(isTransactionsLoading || isReferendumsLoading || (isReferendumsDataLoading && !referendumsData.length)) &&
referendumTxs.length === 0
}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { Chain } from '@domains/chains'
import { useReferenda } from '@domains/referenda'
import { Select } from '@talismn/ui'
import { css } from '@emotion/css'
import useGetReferendums from '@hooks/queries/useGetReferendums'
import { SupportedChainIds } from '@domains/chains/generated-chains'
import clsx from 'clsx'

type Props = {
chain: Chain
Expand All @@ -13,20 +16,30 @@ export const ProposalsDropdown: React.FC<Props> = ({ chain, referendumId, onChan
const { referendums } = useReferenda(chain)
const ongoingReferendums = referendums?.filter(referendum => referendum.isOngoing)

const { data: referendumsData } = useGetReferendums({
chainId: chain.squidIds.chainData as SupportedChainIds,
ids: ongoingReferendums?.map(referendum => String(referendum.index)) ?? [],
})

return (
<Select
className={css`
button {
height: 56px;
}
`}
className={clsx(
'truncate',
css`
button {
height: 56px;
}
`
)}
placeholder="Select proposal to vote on"
value={referendumId}
onChange={onChange}
>
{ongoingReferendums?.map(referendum => (
<Select.Option headlineText={`Proposal #${referendum.index}`} value={referendum.index} key={referendum.index} />
))}
{ongoingReferendums?.map(referendum => {
const { title } = referendumsData?.find(ref => ref?.referendumIndex === referendum.index) || {}
const headlineText = title ? `Proposal #${referendum.index} - ${title}` : `Proposal #${referendum.index}`
return <Select.Option headlineText={headlineText} value={referendum.index} key={referendum.index} />
})}
</Select>
)
}
Loading

0 comments on commit 88ad428

Please sign in to comment.