// Copyright 2017-2026 @pezkuwi/react-components authors & contributors // SPDX-License-Identifier: Apache-2.0 import type { DeriveDemocracyLock } from '@pezkuwi/api-derive/types'; import type { Balance } from '@pezkuwi/types/interfaces'; import type { BN } from '@pezkuwi/util'; import React, { useEffect, useState } from 'react'; import { useBestNumber } from '@pezkuwi/react-hooks'; import { BlockToTime, FormatBalance } from '@pezkuwi/react-query'; import { BN_ZERO, bnMax, formatBalance, formatNumber } from '@pezkuwi/util'; import Icon from './Icon.js'; import { styled } from './styled.js'; import Tooltip from './Tooltip.js'; import { useTranslation } from './translate.js'; interface Props { className?: string; value?: Partial[]; } interface Entry { details: React.ReactNode; headers: React.ReactNode[]; isCountdown: boolean; isFinished: boolean; } interface State { maxBalance: BN; sorted: Entry[]; } let id = 0; // group by header & details // - all unlockable together // - all ongoing together // - unlocks are displayed individually function groupLocks (t: (key: string, options?: { replace: Record }) => string, bestNumber: BN, locks: Partial[] = []): State { return { maxBalance: bnMax(...locks.map(({ balance }) => balance).filter((b): b is Balance => !!b)), sorted: locks .map((info): [Partial, BN] => [info, info.unlockAt && info.unlockAt.gt(bestNumber) ? info.unlockAt.sub(bestNumber) : BN_ZERO]) .sort((a, b) => (a[0].referendumId || BN_ZERO).cmp(b[0].referendumId || BN_ZERO)) .sort((a, b) => a[1].cmp(b[1])) .sort((a, b) => a[0].isFinished === b[0].isFinished ? 0 : (a[0].isFinished ? -1 : 1)) .reduce((sorted: Entry[], [{ balance, isDelegated, isFinished = false, referendumId, vote }, blocks]): Entry[] => { const isCountdown = blocks.gt(BN_ZERO); const header = referendumId && vote ?
#{referendumId.toString()} {formatBalance(balance, { forceUnit: '-' })} {vote.conviction?.toString()}{isDelegated && '/d'}
:
{t('Prior locked voting')}
; const prev = sorted.length ? sorted[sorted.length - 1] : null; if (!prev || (isCountdown || (isFinished !== prev.isFinished))) { sorted.push({ details: (
{isCountdown ? ( ) : isFinished ? t('lock expired') : t('ongoing referendum') }
), headers: [header], isCountdown, isFinished }); } else { prev.headers.push(header); } return sorted; }, []) }; } function DemocracyLocks ({ className = '', value }: Props): React.ReactElement | null { const { t } = useTranslation(); const bestNumber = useBestNumber(); const [trigger] = useState(() => `${Date.now()}-democracy-locks-${++id}`); const [{ maxBalance, sorted }, setState] = useState({ maxBalance: BN_ZERO, sorted: [] }); useEffect((): void => { bestNumber && setState((state): State => { const newState = groupLocks(t, bestNumber, value); // only update when the structure of new is different // - it has a new overall breakdown with sections // - one of the sections has a different number of headers return state.sorted.length !== newState.sorted.length || state.sorted.some((s, i) => s.headers.length !== newState.sorted[i].headers.length) ? newState : state; }); }, [bestNumber, t, value]); if (!sorted.length) { return null; } return ( } value={maxBalance} /> {sorted.map(({ details, headers }, index): React.ReactNode => (
{headers.map((header, index) => (
{header}
))}
{details}
))}
); } const StyledDiv = styled.div` white-space: nowrap; .ui--FormatBalance { display: inline-block; } `; export default React.memo(DemocracyLocks);