Split tips into an own tab (#3487)
This commit is contained in:
parent
9b1821203e
commit
6079e30579
@ -3,36 +3,24 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { DeriveTreasuryProposals } from '@polkadot/api-derive/types';
|
||||
import { BareProps as Props } from '@polkadot/react-components/types';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Button } from '@polkadot/react-components';
|
||||
import { useApi, useCall, useIncrement, useMembers, useIsMountedRef } from '@polkadot/react-hooks';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import ProposalCreate from './ProposalCreate';
|
||||
import Proposals from './Proposals';
|
||||
import Summary from './Summary';
|
||||
import TipCreate from './TipCreate';
|
||||
import Tips from './Tips';
|
||||
|
||||
function Overview ({ className }: Props): React.ReactElement<Props> {
|
||||
interface Props {
|
||||
className?: string;
|
||||
isMember: boolean;
|
||||
members: string[];
|
||||
}
|
||||
|
||||
function Overview ({ className, isMember, members }: Props): React.ReactElement<Props> {
|
||||
const { api } = useApi();
|
||||
const info = useCall<DeriveTreasuryProposals>(api.derive.treasury.proposals);
|
||||
const { isMember, members } = useMembers();
|
||||
|
||||
const mountedRef = useIsMountedRef();
|
||||
const [tipHashTrigger, triggerTipHashes] = useIncrement();
|
||||
const [tipHashes, setTipHashes] = useState<string[] | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
if (tipHashTrigger && mountedRef.current) {
|
||||
api.query.treasury.tips.keys().then((keys) =>
|
||||
mountedRef.current && setTipHashes(
|
||||
keys.map((key) => key.args[0].toHex())
|
||||
)
|
||||
).catch(console.error);
|
||||
}
|
||||
}, [api, tipHashTrigger, mountedRef]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
@ -41,10 +29,6 @@ function Overview ({ className }: Props): React.ReactElement<Props> {
|
||||
proposalCount={info?.proposals.length}
|
||||
/>
|
||||
<Button.Group>
|
||||
<TipCreate
|
||||
members={members}
|
||||
refresh={triggerTipHashes}
|
||||
/>
|
||||
<ProposalCreate />
|
||||
</Button.Group>
|
||||
<Proposals
|
||||
@ -58,11 +42,6 @@ function Overview ({ className }: Props): React.ReactElement<Props> {
|
||||
members={members}
|
||||
proposals={info?.approvals}
|
||||
/>
|
||||
<Tips
|
||||
hashes={tipHashes}
|
||||
isMember={isMember}
|
||||
members={members}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { AccountId, Balance, BlockNumber, OpenTip, OpenTipTo225 } from '@polkadot/types/interfaces';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { AddressSmall, AddressMini, Expander, Icon, LinkExternal, TxButton } from '@polkadot/react-components';
|
||||
import { useAccounts } from '@polkadot/react-hooks';
|
||||
@ -40,44 +40,48 @@ function isCurrentTip (tip: OpenTip | OpenTipTo225): tip is OpenTip {
|
||||
return !!(tip as OpenTip)?.findersFee;
|
||||
}
|
||||
|
||||
function extractTipState (tip: OpenTip | OpenTipTo225, hash: string, allAccounts: string[]): TipState {
|
||||
const closesAt = tip.closes.unwrapOr(null);
|
||||
let finder: AccountId | null = null;
|
||||
let deposit: Balance | null = null;
|
||||
|
||||
if (isCurrentTip(tip)) {
|
||||
finder = tip.finder;
|
||||
deposit = tip.deposit;
|
||||
} else if (tip.finder.isSome) {
|
||||
const finderInfo = tip.finder.unwrap();
|
||||
|
||||
finder = finderInfo[0];
|
||||
deposit = finderInfo[1];
|
||||
}
|
||||
|
||||
const values = tip.tips.map(([, value]) => value).sort((a, b) => a.cmp(b));
|
||||
const midIndex = Math.floor(values.length / 2);
|
||||
const median = values.length
|
||||
? values.length % 2
|
||||
? values[midIndex]
|
||||
: values[midIndex - 1].add(values[midIndex]).divn(2)
|
||||
: BN_ZERO;
|
||||
|
||||
return {
|
||||
closesAt,
|
||||
deposit,
|
||||
finder,
|
||||
isFinder: !!finder && allAccounts.includes(finder.toString()),
|
||||
isTipped: !!values.length,
|
||||
isTipper: tip.tips.some(([address]) => allAccounts.includes(address.toString())),
|
||||
median
|
||||
};
|
||||
}
|
||||
|
||||
function Tip ({ bestNumber, className = '', hash, isMember, members, tip }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { allAccounts } = useAccounts();
|
||||
const [{ closesAt, finder, isFinder, isTipped, isTipper, median }, setTipState] = useState<TipState>({ closesAt: null, deposit: null, finder: null, isFinder: false, isTipped: false, isTipper: false, median: BN_ZERO });
|
||||
|
||||
useEffect((): void => {
|
||||
const closesAt = tip.closes.unwrapOr(null);
|
||||
let finder: AccountId | null = null;
|
||||
let deposit: Balance | null = null;
|
||||
|
||||
if (isCurrentTip(tip)) {
|
||||
finder = tip.finder;
|
||||
deposit = tip.deposit;
|
||||
} else if (tip.finder.isSome) {
|
||||
const finderInfo = tip.finder.unwrap();
|
||||
|
||||
finder = finderInfo[0];
|
||||
deposit = finderInfo[1];
|
||||
}
|
||||
|
||||
const values = tip.tips.map(([, value]) => value).sort((a, b) => a.cmp(b));
|
||||
const midIndex = Math.floor(values.length / 2);
|
||||
const median = values.length
|
||||
? values.length % 2
|
||||
? values[midIndex]
|
||||
: values[midIndex - 1].add(values[midIndex]).divn(2)
|
||||
: BN_ZERO;
|
||||
|
||||
setTipState({
|
||||
closesAt,
|
||||
deposit,
|
||||
finder,
|
||||
isFinder: !!finder && allAccounts.includes(finder.toString()),
|
||||
isTipped: !!values.length,
|
||||
isTipper: tip.tips.some(([address]) => allAccounts.includes(address.toString())),
|
||||
median
|
||||
});
|
||||
}, [allAccounts, hash, tip]);
|
||||
const { closesAt, finder, isFinder, isTipped, isTipper, median } = useMemo(
|
||||
() => extractTipState(tip, hash, allAccounts),
|
||||
[allAccounts, hash, tip]
|
||||
);
|
||||
|
||||
const { reason, tips, who } = tip;
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import { BlockNumber, OpenTip, OpenTipTo225 } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { Option } from '@polkadot/types';
|
||||
@ -21,29 +21,35 @@ interface Props {
|
||||
|
||||
type Tip = [string, OpenTip | OpenTipTo225];
|
||||
|
||||
function extractTips (optTips?: Option<OpenTip>[], hashes?: string[] | null): Tip[] | undefined {
|
||||
if (!hashes || !optTips) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return optTips
|
||||
.map((opt, index): [string, OpenTip | null] => [hashes[index], opt.unwrapOr(null)])
|
||||
.filter((val): val is [string, OpenTip] => !!val[1])
|
||||
.sort((a, b) =>
|
||||
a[1].closes.isNone
|
||||
? b[1].closes.isNone
|
||||
? 0
|
||||
: -1
|
||||
: b[1].closes.isSome
|
||||
? b[1].closes.unwrap().cmp(a[1].closes.unwrap())
|
||||
: 1
|
||||
);
|
||||
}
|
||||
|
||||
function Tips ({ className = '', hashes, isMember, members }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const bestNumber = useCall<BlockNumber>(api.derive.chain.bestNumber);
|
||||
const [tips, setTips] = useState<Tip[] | undefined>();
|
||||
const optTips = useCall<Option<OpenTip>[]>(hashes && api.query.treasury.tips.multi, [hashes]);
|
||||
|
||||
useEffect((): void => {
|
||||
hashes && optTips && setTips(
|
||||
optTips
|
||||
.map((opt, index): [string, OpenTip | null] => [hashes[index], opt.unwrapOr(null)])
|
||||
.filter((val): val is [string, OpenTip] => !!val[1])
|
||||
.sort((a, b) =>
|
||||
a[1].closes.isNone
|
||||
? b[1].closes.isNone
|
||||
? 0
|
||||
: -1
|
||||
: b[1].closes.isSome
|
||||
? b[1].closes.unwrap().cmp(a[1].closes.unwrap())
|
||||
: 1
|
||||
)
|
||||
);
|
||||
}, [hashes, optTips]);
|
||||
const tips = useMemo(
|
||||
() => extractTips(optTips, hashes),
|
||||
[hashes, optTips]
|
||||
);
|
||||
|
||||
const headerRef = useRef([
|
||||
[t('tips'), 'start'],
|
||||
37
packages/page-treasury/src/Tips/index.tsx
Normal file
37
packages/page-treasury/src/Tips/index.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2020 @polkadot/app-treasury authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import React from 'react';
|
||||
import { Button } from '@polkadot/react-components';
|
||||
|
||||
import TipCreate from './TipCreate';
|
||||
import Tips from './Tips';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
hashes?: string[] | null;
|
||||
isMember: boolean;
|
||||
members: string[];
|
||||
trigger: () => void;
|
||||
}
|
||||
|
||||
function TipsEntry ({ className, hashes, isMember, members, trigger }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Button.Group>
|
||||
<TipCreate
|
||||
members={members}
|
||||
refresh={trigger}
|
||||
/>
|
||||
</Button.Group>
|
||||
<Tips
|
||||
hashes={hashes}
|
||||
isMember={isMember}
|
||||
members={members}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(TipsEntry);
|
||||
@ -2,12 +2,14 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Route, Switch } from 'react-router';
|
||||
import { HelpOverlay, Tabs } from '@polkadot/react-components';
|
||||
import { useApi, useIncrement, useIsMountedRef, useMembers } from '@polkadot/react-hooks';
|
||||
|
||||
import basicMd from './md/basic.md';
|
||||
import Overview from './Overview';
|
||||
import Tips from './Tips';
|
||||
|
||||
import { useTranslation } from './translate';
|
||||
|
||||
@ -19,14 +21,34 @@ interface Props {
|
||||
|
||||
function TreasuryApp ({ basePath }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const mountedRef = useIsMountedRef();
|
||||
const [tipHashTrigger, triggerTipHashes] = useIncrement();
|
||||
const { isMember, members } = useMembers();
|
||||
const [tipHashes, setTipHashes] = useState<string[] | null>(null);
|
||||
|
||||
const itemsRef = useRef([
|
||||
useEffect((): void => {
|
||||
if (tipHashTrigger && mountedRef.current) {
|
||||
api.query.treasury.tips.keys().then((keys) =>
|
||||
mountedRef.current && setTipHashes(
|
||||
keys.map((key) => key.args[0].toHex())
|
||||
)
|
||||
).catch(console.error);
|
||||
}
|
||||
}, [api, tipHashTrigger, mountedRef]);
|
||||
|
||||
const items = useMemo(() => [
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'overview',
|
||||
text: t<string>('Treasury overview')
|
||||
},
|
||||
{
|
||||
count: tipHashes?.length,
|
||||
name: 'tips',
|
||||
text: t<string>('Tips')
|
||||
}
|
||||
]);
|
||||
], [t, tipHashes]);
|
||||
|
||||
return (
|
||||
<main className='treasury--App'>
|
||||
@ -34,11 +56,24 @@ function TreasuryApp ({ basePath }: Props): React.ReactElement<Props> {
|
||||
<header>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={itemsRef.current}
|
||||
items={items}
|
||||
/>
|
||||
</header>
|
||||
<Switch>
|
||||
<Route component={Overview} />
|
||||
<Route path={`${basePath}/tips`}>
|
||||
<Tips
|
||||
hashes={tipHashes}
|
||||
isMember={isMember}
|
||||
members={members}
|
||||
trigger={triggerTipHashes}
|
||||
/>
|
||||
</Route>
|
||||
<Route>
|
||||
<Overview
|
||||
isMember={isMember}
|
||||
members={members}
|
||||
/>
|
||||
</Route>
|
||||
</Switch>
|
||||
</main>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user