mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-22 17:07:58 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
// Copyright 2017-2025 @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);
|
||||
Reference in New Issue
Block a user