mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-22 10:07:57 +00:00
147 lines
4.7 KiB
TypeScript
147 lines
4.7 KiB
TypeScript
// 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<DeriveDemocracyLock>[];
|
|
}
|
|
|
|
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, unknown> }) => string, bestNumber: BN, locks: Partial<DeriveDemocracyLock>[] = []): State {
|
|
return {
|
|
maxBalance: bnMax(...locks.map(({ balance }) => balance).filter((b): b is Balance => !!b)),
|
|
sorted: locks
|
|
.map((info): [Partial<DeriveDemocracyLock>, 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
|
|
? <div>#{referendumId.toString()} {formatBalance(balance, { forceUnit: '-' })} {vote.conviction?.toString()}{isDelegated && '/d'}</div>
|
|
: <div>{t('Prior locked voting')}</div>;
|
|
const prev = sorted.length ? sorted[sorted.length - 1] : null;
|
|
|
|
if (!prev || (isCountdown || (isFinished !== prev.isFinished))) {
|
|
sorted.push({
|
|
details: (
|
|
<div className='faded'>
|
|
{isCountdown
|
|
? (
|
|
<BlockToTime
|
|
label={`${t('{{blocks}} blocks', { replace: { blocks: formatNumber(blocks) } })}, `}
|
|
value={blocks}
|
|
/>
|
|
)
|
|
: isFinished
|
|
? t('lock expired')
|
|
: t('ongoing referendum')
|
|
}
|
|
</div>
|
|
),
|
|
headers: [header],
|
|
isCountdown,
|
|
isFinished
|
|
});
|
|
} else {
|
|
prev.headers.push(header);
|
|
}
|
|
|
|
return sorted;
|
|
}, [])
|
|
};
|
|
}
|
|
|
|
function DemocracyLocks ({ className = '', value }: Props): React.ReactElement<Props> | null {
|
|
const { t } = useTranslation();
|
|
const bestNumber = useBestNumber();
|
|
const [trigger] = useState(() => `${Date.now()}-democracy-locks-${++id}`);
|
|
const [{ maxBalance, sorted }, setState] = useState<State>({ 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 (
|
|
<StyledDiv className={className}>
|
|
<FormatBalance
|
|
labelPost={
|
|
<Icon
|
|
icon='clock'
|
|
tooltip={trigger}
|
|
/>
|
|
}
|
|
value={maxBalance}
|
|
/>
|
|
<Tooltip trigger={trigger}>
|
|
{sorted.map(({ details, headers }, index): React.ReactNode => (
|
|
<div
|
|
className='row'
|
|
key={index}
|
|
>
|
|
{headers.map((header, index) => (
|
|
<div key={index}>{header}</div>
|
|
))}
|
|
<div className='faded'>{details}</div>
|
|
</div>
|
|
))}
|
|
</Tooltip>
|
|
</StyledDiv>
|
|
);
|
|
}
|
|
|
|
const StyledDiv = styled.div`
|
|
white-space: nowrap;
|
|
|
|
.ui--FormatBalance {
|
|
display: inline-block;
|
|
}
|
|
`;
|
|
|
|
export default React.memo(DemocracyLocks);
|