From 5c1cfd472d705997d430bccfac087c50bcb5acc9 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sat, 27 Mar 2021 09:19:11 +0100 Subject: [PATCH] Refresh single fund info (#4956) * Refresh single fund info * Media queries * className fix * Countdown on ended * s/count/unique/ * loading * only count in state --- .../page-parachains/src/Crowdloan/Fund.tsx | 62 +++++++++++++++---- .../page-parachains/src/Crowdloan/Funds.tsx | 48 +++++--------- packages/react-hooks/src/useEventTrigger.ts | 16 ++--- 3 files changed, 72 insertions(+), 54 deletions(-) diff --git a/packages/page-parachains/src/Crowdloan/Fund.tsx b/packages/page-parachains/src/Crowdloan/Fund.tsx index b9d6c8b98f..5ca934fc7f 100644 --- a/packages/page-parachains/src/Crowdloan/Fund.tsx +++ b/packages/page-parachains/src/Crowdloan/Fund.tsx @@ -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 { +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 { const { t } = useTranslation(); const { api } = useApi(); const { allAccounts } = useAccounts(); + const [{ myAccounts, uniqueCount }, setContributors] = useState({}); + 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('Ended') } - + {!isOngoing && ( - {retiringLeft && retiring.isTrue && ( - + {isEnded && ( + <> + {retiringLeft && ( + + )} + #{formatNumber(retireEnd)} + )} - {!blocksLeft && <>#{formatNumber(retireEnd)}} )} - + {blocksLeft && ( )} @@ -109,10 +139,16 @@ function Fund ({ bestNumber, className, contributed, isOngoing, value: { info: {
{percentage}
- {contributed && ( - formatNumber(contributed.count) + {uniqueCount && ( + formatNumber(uniqueCount) )} + + + {canDissolve && ( { const { t } = useTranslation(); - const { api } = useApi(); - const trigger = useEventTrigger([api.events.crowdloan.Contributed]); - const [contributors, setContributors] = useState([]); 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 <> ('No active campaigns found')} + empty={value && active && t('No active campaigns found')} header={headerActiveRef.current} > {active?.map((fund) => (
('No completed campaigns found')} + empty={value && ended && t('No completed campaigns found')} header={headedEndedRef.current} > {ended?.map((fund) => ( diff --git a/packages/react-hooks/src/useEventTrigger.ts b/packages/react-hooks/src/useEventTrigger.ts index 0be53b3be7..3774a849a9 100644 --- a/packages/react-hooks/src/useEventTrigger.ts +++ b/packages/react-hooks/src/useEventTrigger.ts @@ -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>(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; }