Refresh single fund info (#4956)

* Refresh single fund info

* Media queries

* className fix

* Countdown on ended

* s/count/unique/

* loading

* only count in state
This commit is contained in:
Jaco Greeff 2021-03-27 09:19:11 +01:00 committed by GitHub
parent eb35ead3ed
commit 5c1cfd472d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 72 additions and 54 deletions

View File

@ -2,15 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
import type BN from 'bn.js';
import type { EventRecord, ParaId } from '@polkadot/types/interfaces';
import type { Campaign } from '../types';
import type { Contributed } from './types';
import React, { useMemo } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { AddressMini, Digits, ParaLink, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi } from '@polkadot/react-hooks';
import { AddressMini, Digits, Icon, ParaLink, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi, useEventTrigger } from '@polkadot/react-hooks';
import { BlockToTime, FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
import { encodeAddress } from '@polkadot/util-crypto';
import { useTranslation } from '../translate';
import FundContribute from './FundContribute';
@ -18,15 +19,40 @@ import FundContribute from './FundContribute';
interface Props {
bestNumber?: BN;
className?: string;
contributed?: Contributed;
isOngoing?: boolean;
value: Campaign;
}
function Fund ({ bestNumber, className, contributed, isOngoing, value: { info: { cap, depositor, end, firstSlot, lastSlot, raised, retiring }, isCapped, isEnded, isRetired, isWinner, paraId, retireEnd } }: Props): React.ReactElement<Props> {
interface Contributions {
uniqueCount?: number;
myAccounts?: string[];
}
function Fund ({ bestNumber, className, isOngoing, value: { childKey, info: { cap, depositor, end, firstSlot, lastSlot, raised, retiring }, isCapped, isEnded, isRetired, isWinner, paraId, retireEnd } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
const [{ myAccounts, uniqueCount }, setContributors] = useState<Contributions>({});
const trigger = useEventTrigger([api.events.crowdloan.Contributed], useCallback(
({ event: { data: [, fundIndex] } }: EventRecord) =>
(fundIndex as ParaId).eq(paraId),
[paraId]
));
useEffect((): void => {
trigger &&
api.rpc.childstate
.getKeys(childKey, '0x')
.then((keys) => setContributors((): Contributions => {
const contributors = keys.map((k) => encodeAddress(k));
return {
myAccounts: contributors.filter((c) => allAccounts.includes(c)),
uniqueCount: contributors.length
};
}))
.catch(console.error);
}, [allAccounts, api, childKey, trigger]);
const isDepositor = useMemo(
(): boolean => {
@ -83,16 +109,20 @@ function Fund ({ bestNumber, className, contributed, isOngoing, value: { info: {
: t<string>('Ended')
}
</td>
<td className='address'><AddressMini value={depositor} /></td>
<td className='address media--1400'><AddressMini value={depositor} /></td>
{!isOngoing && (
<td className='all number together'>
{retiringLeft && retiring.isTrue && (
<BlockToTime value={retiringLeft} />
{isEnded && (
<>
{retiringLeft && (
<BlockToTime value={retiringLeft} />
)}
#{formatNumber(retireEnd)}
</>
)}
{!blocksLeft && <>#{formatNumber(retireEnd)}</>}
</td>
)}
<td className='all number together'>
<td className={`all number together${isOngoing ? '' : ' media--1200'}`}>
{blocksLeft && (
<BlockToTime value={blocksLeft} />
)}
@ -109,10 +139,16 @@ function Fund ({ bestNumber, className, contributed, isOngoing, value: { info: {
<div>{percentage}</div>
</td>
<td className='number'>
{contributed && (
formatNumber(contributed.count)
{uniqueCount && (
formatNumber(uniqueCount)
)}
</td>
<td className='badge'>
<Icon
color={myAccounts?.length ? 'green' : 'gray'}
icon='asterisk'
/>
</td>
<td className='button'>
{canDissolve && (
<TxButton

View File

@ -3,12 +3,10 @@
import type BN from 'bn.js';
import type { Campaign, LeasePeriod } from '../types';
import type { Contributed } from './types';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useMemo, useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { useApi, useEventTrigger } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import Fund from './Fund';
@ -33,48 +31,32 @@ function extractLists (value: Campaign[] | null, leasePeriod?: LeasePeriod): [Ca
function Funds ({ bestNumber, className, leasePeriod, value }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const trigger = useEventTrigger([api.events.crowdloan.Contributed]);
const [contributors, setContributors] = useState<Contributed[]>([]);
const headerActiveRef = useRef([
[t('ongoing'), 'start', 4],
[t('ongoing'), 'start', 2],
[undefined, 'media--1400'],
[],
[t('ending')],
[t('periods')],
[t('raised')],
[t('count')],
[t('unique')],
[undefined, 'badge'],
[]
]);
const headedEndedRef = useRef([
[t('completed'), 'start', 4],
[t('completed'), 'start', 2],
[undefined, 'media--1400'],
[],
[t('retired')],
[t('ending')],
[t('ending'), 'media--1200'],
[t('periods')],
[t('raised')],
[t('count')],
[t('unique')],
[undefined, 'badge'],
[]
]);
useEffect((): void => {
trigger && value &&
Promise
.all(value.map(({ childKey }) => api.rpc.childstate.getKeys(childKey, '0x')))
.then((all) => setContributors(
all.map((keys, index) => ({
accountIds: [], // keys.map((a) => encodeAddress(a)),
count: keys.length,
trieIndex: value[index].info.trieIndex
}))
))
.catch(console.error);
}, [api, trigger, value]);
const findContributions = useCallback(
(trieIndex: BN) => contributors.find((c) => trieIndex.eq(c.trieIndex)),
[contributors]
);
const [active, ended] = useMemo(
() => extractLists(value, leasePeriod),
[leasePeriod, value]
@ -84,13 +66,12 @@ function Funds ({ bestNumber, className, leasePeriod, value }: Props): React.Rea
<>
<Table
className={className}
empty={active && t<string>('No active campaigns found')}
empty={value && active && t<string>('No active campaigns found')}
header={headerActiveRef.current}
>
{active?.map((fund) => (
<Fund
bestNumber={bestNumber}
contributed={findContributions(fund.info.trieIndex)}
isOngoing
key={fund.accountId}
value={fund}
@ -99,13 +80,12 @@ function Funds ({ bestNumber, className, leasePeriod, value }: Props): React.Rea
</Table>
<Table
className={className}
empty={ended && t<string>('No completed campaigns found')}
empty={value && ended && t<string>('No completed campaigns found')}
header={headedEndedRef.current}
>
{ended?.map((fund) => (
<Fund
bestNumber={bestNumber}
contributed={findContributions(fund.info.trieIndex)}
key={fund.accountId}
value={fund}
/>

View File

@ -15,21 +15,23 @@ import { useIsMountedRef } from './useIsMountedRef';
type EventCheck = AugmentedEvent<'promise'> | string | false | undefined | null;
export function useEventTrigger (checks: EventCheck[]): string {
const IDENTITY_FILTER = () => true;
export function useEventTrigger (checks: EventCheck[], filter: (record: EventRecord) => boolean = IDENTITY_FILTER): string {
const { api } = useApi();
const [trigger, setTrigger] = useState('0x00');
const mountedRef = useIsMountedRef();
const eventRecords = useCall<Vec<EventRecord>>(api.query.system.events);
useEffect((): void => {
mountedRef.current && eventRecords && eventRecords.filter(({ event }) =>
event && checks.some((check) => check && (
mountedRef.current && eventRecords && eventRecords.filter((r) =>
r.event && checks.some((check) => check && (
isString(check)
? event.section === check
: check.is(event)
))
? r.event.section === check
: check.is(r.event)
)) && filter(r)
).length && setTrigger(() => eventRecords.createdAtHash?.toHex() || '0x00');
}, [eventRecords, checks, mountedRef]);
}, [eventRecords, checks, filter, mountedRef]);
return trigger;
}