From 20c36b8cb2620befde28a618de0af374ffdc71aa Mon Sep 17 00:00:00 2001 From: Feng Xiaoyan <40415647+FengXiaoyan-f@users.noreply.github.com> Date: Sun, 22 Nov 2020 14:16:20 +0800 Subject: [PATCH 01/29] new menu --- packages/apps-routing/src/competitive_list.ts | 20 ++++++++++++++++ packages/apps-routing/src/data_statistics.ts | 20 ++++++++++++++++ packages/apps-routing/src/index.ts | 12 ++++++++-- packages/apps-routing/src/issuance_fund.ts | 23 +++++++++++++++++++ packages/apps-routing/src/knowledge_power.ts | 8 ++++--- .../apps-routing/src/roles_and_role_groups.ts | 20 ++++++++++++++++ packages/apps-routing/src/types.ts | 2 +- .../apps/public/locales/en/apps-routing.json | 6 ++++- .../apps/public/locales/zh/translation.json | 5 +++- packages/apps/src/Menu/ChainInfo.tsx | 4 ++-- packages/apps/src/Menu/index.tsx | 6 ++--- packages/react-query/src/Chain.tsx | 5 ++-- 12 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 packages/apps-routing/src/competitive_list.ts create mode 100644 packages/apps-routing/src/data_statistics.ts create mode 100644 packages/apps-routing/src/issuance_fund.ts create mode 100644 packages/apps-routing/src/roles_and_role_groups.ts diff --git a/packages/apps-routing/src/competitive_list.ts b/packages/apps-routing/src/competitive_list.ts new file mode 100644 index 000000000000..70e5e9addcc2 --- /dev/null +++ b/packages/apps-routing/src/competitive_list.ts @@ -0,0 +1,20 @@ +// Copyright 2017-2020 @polkadot/apps-routing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { TFunction } from 'i18next'; +import { Route } from './types'; + +import Component, { useCounter } from '@polkadot/app-settings'; + +export default function create (t: TFunction): Route { + return { + Component, + display: { + needsApi: [] + }, + group: 'chain_application', + icon: 'database', + name: 'competitive_list', + text: t('nav.competitive_list', 'Competitive List', { ns: 'apps-routing' }) + }; +} diff --git a/packages/apps-routing/src/data_statistics.ts b/packages/apps-routing/src/data_statistics.ts new file mode 100644 index 000000000000..52a60cd272e1 --- /dev/null +++ b/packages/apps-routing/src/data_statistics.ts @@ -0,0 +1,20 @@ +// Copyright 2017-2020 @polkadot/apps-routing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { TFunction } from 'i18next'; +import { Route } from './types'; + +import Component, { useCounter } from '@polkadot/app-settings'; + +export default function create (t: TFunction): Route { + return { + Component, + display: { + needsApi: [] + }, + group: 'chain_application', + icon: 'database', + name: 'data_statistics', + text: t('nav.data_statistics', 'Data Statistics', { ns: 'apps-routing' }) + }; +} diff --git a/packages/apps-routing/src/index.ts b/packages/apps-routing/src/index.ts index 34ed299f6edb..41dc52eaf705 100644 --- a/packages/apps-routing/src/index.ts +++ b/packages/apps-routing/src/index.ts @@ -27,8 +27,12 @@ import sudo from './sudo'; import techcomm from './techcomm'; import transfer from './transfer'; import treasury from './treasury'; -import chain_application from './chain_application'; +//import chain_application from './chain_application'; import knowledge_power from './knowledge_power'; +import competitive_list from './competitive_list'; +import roles_and_role_groups from './roles_and_role_groups'; +import data_statistics from './data_statistics'; +import issuance_fund from './issuance_fund'; export default function create (t: TFunction): Routes { return [ @@ -43,6 +47,7 @@ export default function create (t: TFunction): Routes { democracy(t), council(t), treasury(t), + issuance_fund(t), techcomm(t), parachains(t), society(t), @@ -56,6 +61,9 @@ export default function create (t: TFunction): Routes { js(t), settings(t), knowledge_power(t), - chain_application(t), + // chain_application(t), + competitive_list(t), + roles_and_role_groups(t), + data_statistics(t), ]; } diff --git a/packages/apps-routing/src/issuance_fund.ts b/packages/apps-routing/src/issuance_fund.ts new file mode 100644 index 000000000000..37baf873cd27 --- /dev/null +++ b/packages/apps-routing/src/issuance_fund.ts @@ -0,0 +1,23 @@ +// Copyright 2017-2020 @polkadot/apps-routing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { TFunction } from 'i18next'; +import { Route } from './types'; + +import Component, { useCounter } from '@polkadot/app-treasury'; + +export default function create (t: TFunction): Route { + return { + Component, + display: { + needsApi: [ + 'tx.treasury.proposeSpend' + ] + }, + group: 'governance', + icon: 'gem', + name: 'issuance_fund', + text: t('nav.issuance_fund', 'Issuance Fund', { ns: 'apps-routing' }), + useCounter + }; +} diff --git a/packages/apps-routing/src/knowledge_power.ts b/packages/apps-routing/src/knowledge_power.ts index 1a00792ed5bc..74153ffe3d94 100644 --- a/packages/apps-routing/src/knowledge_power.ts +++ b/packages/apps-routing/src/knowledge_power.ts @@ -9,11 +9,13 @@ import Component, { useCounter } from '@ctt/app-knowledge-power'; export default function create (t: TFunction): Route { return { Component, - display: {}, - group: 'knowledge_power', + display: { + needsApi: [] + }, + group: 'chain_application', icon: 'lightbulb-on', name: 'knowledge_power', text: t('nav.knowledge_power', 'Knowledge Power', { ns: 'apps-routing' }), - useCounter + /* useCounter */ }; } diff --git a/packages/apps-routing/src/roles_and_role_groups.ts b/packages/apps-routing/src/roles_and_role_groups.ts new file mode 100644 index 000000000000..8aa60ee47507 --- /dev/null +++ b/packages/apps-routing/src/roles_and_role_groups.ts @@ -0,0 +1,20 @@ +// Copyright 2017-2020 @polkadot/apps-routing authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { TFunction } from 'i18next'; +import { Route } from './types'; + +import Component, { useCounter } from '@polkadot/app-settings'; + +export default function create (t: TFunction): Route { + return { + Component, + display: { + needsApi: [] + }, + group: 'chain_application', + icon: 'database', + name: 'roles_and_role_groups', + text: t('nav.roles_and_role_groups', 'Roles And Role Groups', { ns: 'apps-routing' }) + }; +} diff --git a/packages/apps-routing/src/types.ts b/packages/apps-routing/src/types.ts index 56b5444592c2..3b61faa253b6 100644 --- a/packages/apps-routing/src/types.ts +++ b/packages/apps-routing/src/types.ts @@ -4,7 +4,7 @@ import { IconName } from '@fortawesome/fontawesome-svg-core'; import { AppProps, BareProps } from '@polkadot/react-components/types'; -export type RouteGroup = 'accounts' | 'developer' | 'governance' | 'network' | 'settings' | 'knowledge_power' | 'chain_application'; +export type RouteGroup = 'accounts' | 'developer' | 'governance' | 'network' | 'chain_application' | 'settings';//| 'knowledge_power' export interface RouteProps extends AppProps, BareProps { location: any; diff --git a/packages/apps/public/locales/en/apps-routing.json b/packages/apps/public/locales/en/apps-routing.json index 7f1ca4828045..8ce58613dba6 100644 --- a/packages/apps/public/locales/en/apps-routing.json +++ b/packages/apps/public/locales/en/apps-routing.json @@ -23,5 +23,9 @@ "nav.transfer": "Transfer", "nav.treasury": "Treasury", "nav.knowledge_power": "Knowledge Power", - "nav.chain_application": "Chain Application" + "nav.competitive_list": "Competitive List", + "nav.roles_and_role_groups": "Roles And Role Groups", + "nav.data_statistics": "Data Statistics", + "nav.issuance_fund": "Issuance Fund" + //"nav.chain_application": "Chain Application" } diff --git a/packages/apps/public/locales/zh/translation.json b/packages/apps/public/locales/zh/translation.json index fef69514ecb7..ec54fc2c1291 100644 --- a/packages/apps/public/locales/zh/translation.json +++ b/packages/apps/public/locales/zh/translation.json @@ -1117,8 +1117,11 @@ "nav.toolbox": "工具箱", "nav.transfer": "转账", "nav.treasury": "财政", + "nav.issuance_fund": "发行基金", "nav.knowledge_power": "知识算力", - "nav.chain_application": "链应用", + "nav.competitive_list": "竞争性榜单", + "nav.roles_and_role_groups": "角色与角色组", + "nav.data_statistics": "数据统计", "nay": "不", "new account": "新建账户", "new address": "新建地址", diff --git a/packages/apps/src/Menu/ChainInfo.tsx b/packages/apps/src/Menu/ChainInfo.tsx index 4e75f2a66bea..4ae91b6db6e7 100644 --- a/packages/apps/src/Menu/ChainInfo.tsx +++ b/packages/apps/src/Menu/ChainInfo.tsx @@ -33,9 +33,9 @@ function ChainInfo ({ className }: Props): React.ReactElement {
- {runtimeVersion && ( + {/*runtimeVersion && (
{t('version {{version}}', { replace: { version: runtimeVersion.specVersion.toNumber() } })}
- )} + )*/} (); function createExternals (t: TFunction): ItemRoute[] { return [ - { href: 'https://github.com/polkadot-js/apps', icon: 'code-branch', name: 'github', text: t('nav.github', 'GitHub', { ns: 'apps-routing' }) }, - { href: 'https://wiki.polkadot.network', icon: 'book', name: 'wiki', text: t('nav.wiki', 'Wiki', { ns: 'apps-routing' }) } + { href: 'https://github.com/CTT-block-chain/web-wallet', icon: 'code-branch', name: 'github', text: t('nav.github', 'GitHub', { ns: 'apps-routing' }) }, + /* { href: 'https://wiki.polkadot.network', icon: 'book', name: 'wiki', text: t('nav.wiki', 'Wiki', { ns: 'apps-routing' }) } */ ]; } @@ -101,7 +101,7 @@ function Menu ({ className = '' }: Props): React.ReactElement { developer: t('Developer'), governance: t('Governance'), network: t('Network'), - knowledge_power: t('Knowledge Power'), + //knowledge_power: t('Knowledge Power'), chain_application: t('Chain Application'), settings: t('Settings') }); diff --git a/packages/react-query/src/Chain.tsx b/packages/react-query/src/Chain.tsx index 37b89a8002ab..dfbadb5f3bb3 100644 --- a/packages/react-query/src/Chain.tsx +++ b/packages/react-query/src/Chain.tsx @@ -15,10 +15,11 @@ interface Props { function Chain ({ children, className = '', label }: Props): React.ReactElement { const { t } = useTranslation(); const { systemChain } = useApi(); - + const systemChainNew='测试版v1.0'; return (
- {label || ''}{systemChain || t('Unknown')}{children} + {/*label || ''*/}{/*systemChain || t('Unknown')*/}{/*children*/} + {systemChainNew}
); } From e5cbc8a529f4e488276efd50179cf87d758341a7 Mon Sep 17 00:00:00 2001 From: Feng Xiaoyan <40415647+FengXiaoyan-f@users.noreply.github.com> Date: Tue, 24 Nov 2020 11:55:22 +0800 Subject: [PATCH 02/29] Add some new pages --- ...n_application.ts => chain_application1.ts} | 6 +- packages/apps-routing/src/competitive_list.ts | 2 +- packages/apps-routing/src/data_statistics.ts | 2 +- packages/apps-routing/src/issuance_fund.ts | 2 +- packages/apps-routing/src/knowledge_power.ts | 9 +- .../apps-routing/src/roles_and_role_groups.ts | 2 +- .../apps/public/locales/zh/translation.json | 56 ++ packages/apps/src/Menu/ChainInfo.tsx | 2 + packages/apps/src/Menu/NodeInfo.tsx | 1 - packages/apps/src/Menu/index.tsx | 5 +- packages/page-competitive-list/.skip-build | 0 packages/page-competitive-list/.skip-npm | 0 packages/page-competitive-list/LICENSE | 201 +++++++ packages/page-competitive-list/README.md | 1 + packages/page-competitive-list/package.json | 22 + .../src/Accounts/Account.tsx | 436 ++++++++++++++++ .../src/Accounts/Banner.tsx | 27 + .../src/Accounts/BannerClaims.tsx | 25 + .../src/Accounts/BannerExtension.tsx | 83 +++ .../src/Accounts/index.tsx | 202 +++++++ .../src/Accounts/types.ts | 31 ++ .../src/Accounts/useKnownAddresses.ts | 15 + .../src/Accounts/useMultisigApprovals.ts | 50 ++ .../src/Accounts/useProxies.ts | 75 +++ .../src/CreateAccount.slow.spec.tsx | 105 ++++ .../src/GoodsModelList/Account.tsx | 366 +++++++++++++ .../src/GoodsModelList/Banner.tsx | 27 + .../src/GoodsModelList/BannerClaims.tsx | 25 + .../src/GoodsModelList/BannerExtension.tsx | 83 +++ .../src/GoodsModelList/index.tsx | 204 ++++++++ .../src/GoodsModelList/types.ts | 31 ++ .../src/GoodsModelList/useKnownAddresses.ts | 15 + .../GoodsModelList/useMultisigApprovals.ts | 50 ++ .../src/GoodsModelList/useProxies.ts | 75 +++ .../src/Sidebar/Balances.tsx | 56 ++ .../src/Sidebar/Flags.tsx | 96 ++++ .../src/Sidebar/Identity.tsx | 211 ++++++++ .../src/Sidebar/Multisig.tsx | 55 ++ .../src/Sidebar/RegistrarJudgement.tsx | 82 +++ .../src/Sidebar/Sidebar.tsx | 304 +++++++++++ .../src/Sidebar/index.tsx | 42 ++ .../src/WholeNetworkList/Account.tsx | 363 +++++++++++++ .../src/WholeNetworkList/Banner.tsx | 27 + .../src/WholeNetworkList/BannerClaims.tsx | 25 + .../src/WholeNetworkList/BannerExtension.tsx | 83 +++ .../src/WholeNetworkList/index.tsx | 204 ++++++++ .../src/WholeNetworkList/types.ts | 31 ++ .../src/WholeNetworkList/useKnownAddresses.ts | 15 + .../WholeNetworkList/useMultisigApprovals.ts | 50 ++ .../src/WholeNetworkList/useProxies.ts | 75 +++ packages/page-competitive-list/src/index.tsx | 65 +++ .../page-competitive-list/src/md/basic.md | 51 ++ .../src/modals/Backup.tsx | 95 ++++ .../src/modals/ChangePass.tsx | 156 ++++++ .../src/modals/Create.tsx | 494 ++++++++++++++++++ .../src/modals/CreateConfirmation.tsx | 63 +++ .../src/modals/Delegate.tsx | 134 +++++ .../src/modals/Derive.tsx | 298 +++++++++++ .../src/modals/ExternalWarning.tsx | 24 + .../src/modals/IdentityMain.tsx | 275 ++++++++++ .../src/modals/IdentitySub.tsx | 183 +++++++ .../src/modals/Import.tsx | 165 ++++++ .../src/modals/InputValidateAmount.tsx | 56 ++ .../src/modals/Ledger.tsx | 120 +++++ .../src/modals/MultisigApprove.tsx | 252 +++++++++ .../src/modals/MultisigCreate.tsx | 149 ++++++ .../src/modals/PasswordInput.tsx | 76 +++ .../src/modals/ProxiedAdd.tsx | 122 +++++ .../src/modals/ProxyOverview.tsx | 351 +++++++++++++ .../page-competitive-list/src/modals/Qr.tsx | 175 +++++++ .../src/modals/RecoverAccount.tsx | 52 ++ .../src/modals/RecoverSetup.tsx | 103 ++++ .../src/modals/Transfer.tsx | 220 ++++++++ .../src/modals/Undelegate.tsx | 51 ++ .../src/test-support/MemoryStore.ts | 34 ++ .../page-competitive-list/src/translate.ts | 8 + packages/page-competitive-list/src/types.ts | 34 ++ .../page-competitive-list/src/useCounter.ts | 10 + packages/page-competitive-list/src/util.tsx | 64 +++ packages/page-data-statistics/.skip-build | 0 packages/page-data-statistics/.skip-npm | 0 packages/page-data-statistics/LICENSE | 201 +++++++ packages/page-data-statistics/README.md | 1 + packages/page-data-statistics/package.json | 22 + .../src/Accounts/Account.tsx | 436 ++++++++++++++++ .../src/Accounts/Banner.tsx | 27 + .../src/Accounts/BannerClaims.tsx | 25 + .../src/Accounts/BannerExtension.tsx | 83 +++ .../src/Accounts/index.tsx | 202 +++++++ .../src/Accounts/types.ts | 31 ++ .../src/Accounts/useKnownAddresses.ts | 15 + .../src/Accounts/useMultisigApprovals.ts | 50 ++ .../src/Accounts/useProxies.ts | 75 +++ .../src/CreateAccount.slow.spec.tsx | 105 ++++ .../src/KptRedemption/Account.tsx | 362 +++++++++++++ .../src/KptRedemption/Banner.tsx | 27 + .../src/KptRedemption/BannerClaims.tsx | 25 + .../src/KptRedemption/BannerExtension.tsx | 83 +++ .../src/KptRedemption/index.tsx | 204 ++++++++ .../src/KptRedemption/types.ts | 31 ++ .../src/KptRedemption/useKnownAddresses.ts | 15 + .../src/KptRedemption/useMultisigApprovals.ts | 50 ++ .../src/KptRedemption/useProxies.ts | 75 +++ .../src/ModelKptIssuance/Account.tsx | 360 +++++++++++++ .../src/ModelKptIssuance/Banner.tsx | 27 + .../src/ModelKptIssuance/BannerClaims.tsx | 25 + .../src/ModelKptIssuance/BannerExtension.tsx | 83 +++ .../src/ModelKptIssuance/index.tsx | 204 ++++++++ .../src/ModelKptIssuance/types.ts | 31 ++ .../src/ModelKptIssuance/useKnownAddresses.ts | 15 + .../ModelKptIssuance/useMultisigApprovals.ts | 50 ++ .../src/ModelKptIssuance/useProxies.ts | 75 +++ .../src/Sidebar/Balances.tsx | 56 ++ .../src/Sidebar/Flags.tsx | 96 ++++ .../src/Sidebar/Identity.tsx | 211 ++++++++ .../src/Sidebar/Multisig.tsx | 55 ++ .../src/Sidebar/RegistrarJudgement.tsx | 82 +++ .../src/Sidebar/Sidebar.tsx | 304 +++++++++++ .../src/Sidebar/index.tsx | 42 ++ packages/page-data-statistics/src/index.tsx | 65 +++ packages/page-data-statistics/src/md/basic.md | 51 ++ .../src/modals/Backup.tsx | 95 ++++ .../src/modals/ChangePass.tsx | 156 ++++++ .../src/modals/Create.tsx | 494 ++++++++++++++++++ .../src/modals/CreateConfirmation.tsx | 63 +++ .../src/modals/Delegate.tsx | 134 +++++ .../src/modals/Derive.tsx | 298 +++++++++++ .../src/modals/ExternalWarning.tsx | 24 + .../src/modals/IdentityMain.tsx | 275 ++++++++++ .../src/modals/IdentitySub.tsx | 183 +++++++ .../src/modals/Import.tsx | 165 ++++++ .../src/modals/InputValidateAmount.tsx | 56 ++ .../src/modals/Ledger.tsx | 120 +++++ .../src/modals/MultisigApprove.tsx | 252 +++++++++ .../src/modals/MultisigCreate.tsx | 149 ++++++ .../src/modals/PasswordInput.tsx | 76 +++ .../src/modals/ProxiedAdd.tsx | 122 +++++ .../src/modals/ProxyOverview.tsx | 351 +++++++++++++ .../page-data-statistics/src/modals/Qr.tsx | 175 +++++++ .../src/modals/RecoverAccount.tsx | 52 ++ .../src/modals/RecoverSetup.tsx | 103 ++++ .../src/modals/Transfer.tsx | 220 ++++++++ .../src/modals/Undelegate.tsx | 51 ++ .../src/test-support/MemoryStore.ts | 34 ++ .../page-data-statistics/src/translate.ts | 8 + packages/page-data-statistics/src/types.ts | 34 ++ .../page-data-statistics/src/useCounter.ts | 10 + packages/page-data-statistics/src/util.tsx | 64 +++ packages/page-issuance-fund/.skip-build | 0 packages/page-issuance-fund/.skip-npm | 0 packages/page-issuance-fund/LICENSE | 201 +++++++ packages/page-issuance-fund/README.md | 1 + packages/page-issuance-fund/package.json | 19 + .../src/FinancingFund/Council.tsx | 113 ++++ .../src/FinancingFund/Proposal.tsx | 96 ++++ .../src/FinancingFund/ProposalCreate.tsx | 117 +++++ .../src/FinancingFund/Proposals.tsx | 66 +++ .../src/FinancingFund/Summary.tsx | 90 ++++ .../src/FinancingFund/index.tsx | 55 ++ .../src/ModelFund/Council.tsx | 113 ++++ .../src/ModelFund/Proposal.tsx | 99 ++++ .../src/ModelFund/ProposalCreate.tsx | 117 +++++ .../src/ModelFund/Proposals.tsx | 66 +++ .../src/ModelFund/Summary.tsx | 103 ++++ .../src/ModelFund/Summary2.tsx | 95 ++++ .../src/ModelFund/index.tsx | 53 ++ .../Council.tsx | 113 ++++ .../Proposal.tsx | 96 ++++ .../ProposalCreate.tsx | 117 +++++ .../Proposals.tsx | 66 +++ .../Summary.tsx | 90 ++++ .../index.tsx | 55 ++ packages/page-issuance-fund/src/Tips/Tip.tsx | 214 ++++++++ .../page-issuance-fund/src/Tips/TipCreate.tsx | 130 +++++ .../src/Tips/TipEndorse.tsx | 98 ++++ .../page-issuance-fund/src/Tips/TipReason.tsx | 31 ++ packages/page-issuance-fund/src/Tips/Tips.tsx | 116 ++++ .../page-issuance-fund/src/Tips/index.tsx | 87 +++ packages/page-issuance-fund/src/index.tsx | 88 ++++ packages/page-issuance-fund/src/md/basic.md | 3 + packages/page-issuance-fund/src/translate.ts | 8 + packages/page-issuance-fund/src/useCounter.ts | 20 + packages/page-knowledge-power/README.md | 2 +- packages/page-knowledge-power/package.json | 13 +- .../src/Accounts/Account.tsx | 436 ++++++++++++++++ .../src/Accounts/Banner.tsx | 27 + .../src/Accounts/BannerClaims.tsx | 25 + .../src/Accounts/BannerExtension.tsx | 83 +++ .../src/Accounts/index.tsx | 202 +++++++ .../src/Accounts/types.ts | 31 ++ .../src/Accounts/useKnownAddresses.ts | 15 + .../src/Accounts/useMultisigApprovals.ts | 50 ++ .../src/Accounts/useProxies.ts | 75 +++ .../Account.tsx | 359 +++++++++++++ .../CompetitiveReviewParticipation/Banner.tsx | 27 + .../BannerClaims.tsx | 25 + .../BannerExtension.tsx | 83 +++ .../CompetitiveReviewParticipation/index.tsx | 204 ++++++++ .../CompetitiveReviewParticipation/types.ts | 31 ++ .../useKnownAddresses.ts | 15 + .../useMultisigApprovals.ts | 50 ++ .../useProxies.ts | 75 +++ .../src/CreateAccount.slow.spec.tsx | 105 ++++ .../src/DocumentKP/Account.tsx | 358 +++++++++++++ .../src/DocumentKP/Banner.tsx | 27 + .../src/DocumentKP/BannerClaims.tsx | 25 + .../src/DocumentKP/BannerExtension.tsx | 83 +++ .../src/DocumentKP/index.tsx | 204 ++++++++ .../src/DocumentKP/types.ts | 31 ++ .../src/DocumentKP/useKnownAddresses.ts | 15 + .../src/DocumentKP/useMultisigApprovals.ts | 50 ++ .../src/DocumentKP/useProxies.ts | 75 +++ .../src/ExpErienceGoodsKP/Account.tsx | 355 +++++++++++++ .../src/ExpErienceGoodsKP/Banner.tsx | 27 + .../src/ExpErienceGoodsKP/BannerClaims.tsx | 25 + .../src/ExpErienceGoodsKP/BannerExtension.tsx | 83 +++ .../src/ExpErienceGoodsKP/index.tsx | 204 ++++++++ .../src/ExpErienceGoodsKP/types.ts | 31 ++ .../ExpErienceGoodsKP/useKnownAddresses.ts | 15 + .../ExpErienceGoodsKP/useMultisigApprovals.ts | 50 ++ .../src/ExpErienceGoodsKP/useProxies.ts | 75 +++ .../src/Sidebar/Balances.tsx | 4 +- .../src/Sidebar/Sidebar.tsx | 4 +- .../page-knowledge-power/src/Vanity/Match.tsx | 110 ++++ .../src/Vanity/bipWorker.ts | 25 + .../page-knowledge-power/src/Vanity/index.tsx | 276 ++++++++++ packages/page-knowledge-power/src/index.tsx | 39 +- .../src/modals/Backup.tsx | 95 ++++ .../src/modals/ChangePass.tsx | 156 ++++++ .../src/modals/Create.tsx | 494 ++++++++++++++++++ .../src/modals/CreateConfirmation.tsx | 63 +++ .../src/modals/Delegate.tsx | 134 +++++ .../src/modals/Derive.tsx | 298 +++++++++++ .../src/modals/ExternalWarning.tsx | 24 + .../src/modals/IdentityMain.tsx | 275 ++++++++++ .../src/modals/IdentitySub.tsx | 183 +++++++ .../src/modals/Import.tsx | 165 ++++++ .../src/modals/InputValidateAmount.tsx | 56 ++ .../src/modals/Ledger.tsx | 120 +++++ .../src/modals/MultisigApprove.tsx | 252 +++++++++ .../src/modals/MultisigCreate.tsx | 149 ++++++ .../src/modals/PasswordInput.tsx | 76 +++ .../src/modals/ProxiedAdd.tsx | 122 +++++ .../src/modals/ProxyOverview.tsx | 351 +++++++++++++ .../page-knowledge-power/src/modals/Qr.tsx | 175 +++++++ .../src/modals/RecoverAccount.tsx | 52 ++ .../src/modals/RecoverSetup.tsx | 103 ++++ .../src/modals/Transfer.tsx | 220 ++++++++ .../src/modals/Undelegate.tsx | 51 ++ .../page-roles-and-role-groups/.skip-build | 0 packages/page-roles-and-role-groups/.skip-npm | 0 packages/page-roles-and-role-groups/LICENSE | 201 +++++++ packages/page-roles-and-role-groups/README.md | 1 + .../page-roles-and-role-groups/package.json | 22 + .../src/Accounts/Account.tsx | 436 ++++++++++++++++ .../src/Accounts/Banner.tsx | 27 + .../src/Accounts/BannerClaims.tsx | 25 + .../src/Accounts/BannerExtension.tsx | 83 +++ .../src/Accounts/index.tsx | 202 +++++++ .../src/Accounts/types.ts | 31 ++ .../src/Accounts/useKnownAddresses.ts | 15 + .../src/Accounts/useMultisigApprovals.ts | 50 ++ .../src/Accounts/useProxies.ts | 75 +++ .../src/CreateAccount.slow.spec.tsx | 105 ++++ .../src/RoleGroups/Account.tsx | 255 +++++++++ .../src/RoleGroups/Banner.tsx | 27 + .../src/RoleGroups/BannerClaims.tsx | 25 + .../src/RoleGroups/BannerExtension.tsx | 83 +++ .../src/RoleGroups/index.tsx | 202 +++++++ .../src/RoleGroups/types.ts | 31 ++ .../src/RoleGroups/useKnownAddresses.ts | 15 + .../src/RoleGroups/useMultisigApprovals.ts | 50 ++ .../src/RoleGroups/useProxies.ts | 75 +++ .../src/Roles/Account.tsx | 359 +++++++++++++ .../src/Roles/Banner.tsx | 27 + .../src/Roles/BannerClaims.tsx | 25 + .../src/Roles/BannerExtension.tsx | 83 +++ .../src/Roles/index.tsx | 202 +++++++ .../src/Roles/types.ts | 31 ++ .../src/Roles/useKnownAddresses.ts | 15 + .../src/Roles/useMultisigApprovals.ts | 50 ++ .../src/Roles/useProxies.ts | 75 +++ .../src/Sidebar/Balances.tsx | 56 ++ .../src/Sidebar/Flags.tsx | 96 ++++ .../src/Sidebar/Identity.tsx | 211 ++++++++ .../src/Sidebar/Multisig.tsx | 55 ++ .../src/Sidebar/RegistrarJudgement.tsx | 82 +++ .../src/Sidebar/Sidebar.tsx | 304 +++++++++++ .../src/Sidebar/index.tsx | 42 ++ .../page-roles-and-role-groups/src/index.tsx | 65 +++ .../src/md/basic.md | 51 ++ .../src/modals/Backup.tsx | 95 ++++ .../src/modals/ChangePass.tsx | 156 ++++++ .../src/modals/Create.tsx | 494 ++++++++++++++++++ .../src/modals/CreateConfirmation.tsx | 63 +++ .../src/modals/Delegate.tsx | 134 +++++ .../src/modals/Derive.tsx | 298 +++++++++++ .../src/modals/ExternalWarning.tsx | 24 + .../src/modals/IdentityMain.tsx | 275 ++++++++++ .../src/modals/IdentitySub.tsx | 183 +++++++ .../src/modals/Import.tsx | 165 ++++++ .../src/modals/InputValidateAmount.tsx | 56 ++ .../src/modals/Ledger.tsx | 120 +++++ .../src/modals/MultisigApprove.tsx | 252 +++++++++ .../src/modals/MultisigCreate.tsx | 149 ++++++ .../src/modals/PasswordInput.tsx | 76 +++ .../src/modals/ProxiedAdd.tsx | 122 +++++ .../src/modals/ProxyOverview.tsx | 351 +++++++++++++ .../src/modals/Qr.tsx | 175 +++++++ .../src/modals/RecoverAccount.tsx | 52 ++ .../src/modals/RecoverSetup.tsx | 103 ++++ .../src/modals/Transfer.tsx | 220 ++++++++ .../src/modals/Undelegate.tsx | 51 ++ .../src/test-support/MemoryStore.ts | 34 ++ .../src/translate.ts | 8 + .../page-roles-and-role-groups/src/types.ts | 34 ++ .../src/useCounter.ts | 10 + .../page-roles-and-role-groups/src/util.tsx | 64 +++ tsconfig.json | 10 +- 319 files changed, 33662 insertions(+), 27 deletions(-) rename packages/apps-routing/src/{chain_application.ts => chain_application1.ts} (89%) create mode 100644 packages/page-competitive-list/.skip-build create mode 100644 packages/page-competitive-list/.skip-npm create mode 100644 packages/page-competitive-list/LICENSE create mode 100644 packages/page-competitive-list/README.md create mode 100644 packages/page-competitive-list/package.json create mode 100644 packages/page-competitive-list/src/Accounts/Account.tsx create mode 100644 packages/page-competitive-list/src/Accounts/Banner.tsx create mode 100644 packages/page-competitive-list/src/Accounts/BannerClaims.tsx create mode 100644 packages/page-competitive-list/src/Accounts/BannerExtension.tsx create mode 100644 packages/page-competitive-list/src/Accounts/index.tsx create mode 100644 packages/page-competitive-list/src/Accounts/types.ts create mode 100644 packages/page-competitive-list/src/Accounts/useKnownAddresses.ts create mode 100644 packages/page-competitive-list/src/Accounts/useMultisigApprovals.ts create mode 100644 packages/page-competitive-list/src/Accounts/useProxies.ts create mode 100644 packages/page-competitive-list/src/CreateAccount.slow.spec.tsx create mode 100644 packages/page-competitive-list/src/GoodsModelList/Account.tsx create mode 100644 packages/page-competitive-list/src/GoodsModelList/Banner.tsx create mode 100644 packages/page-competitive-list/src/GoodsModelList/BannerClaims.tsx create mode 100644 packages/page-competitive-list/src/GoodsModelList/BannerExtension.tsx create mode 100644 packages/page-competitive-list/src/GoodsModelList/index.tsx create mode 100644 packages/page-competitive-list/src/GoodsModelList/types.ts create mode 100644 packages/page-competitive-list/src/GoodsModelList/useKnownAddresses.ts create mode 100644 packages/page-competitive-list/src/GoodsModelList/useMultisigApprovals.ts create mode 100644 packages/page-competitive-list/src/GoodsModelList/useProxies.ts create mode 100644 packages/page-competitive-list/src/Sidebar/Balances.tsx create mode 100644 packages/page-competitive-list/src/Sidebar/Flags.tsx create mode 100644 packages/page-competitive-list/src/Sidebar/Identity.tsx create mode 100644 packages/page-competitive-list/src/Sidebar/Multisig.tsx create mode 100644 packages/page-competitive-list/src/Sidebar/RegistrarJudgement.tsx create mode 100644 packages/page-competitive-list/src/Sidebar/Sidebar.tsx create mode 100644 packages/page-competitive-list/src/Sidebar/index.tsx create mode 100644 packages/page-competitive-list/src/WholeNetworkList/Account.tsx create mode 100644 packages/page-competitive-list/src/WholeNetworkList/Banner.tsx create mode 100644 packages/page-competitive-list/src/WholeNetworkList/BannerClaims.tsx create mode 100644 packages/page-competitive-list/src/WholeNetworkList/BannerExtension.tsx create mode 100644 packages/page-competitive-list/src/WholeNetworkList/index.tsx create mode 100644 packages/page-competitive-list/src/WholeNetworkList/types.ts create mode 100644 packages/page-competitive-list/src/WholeNetworkList/useKnownAddresses.ts create mode 100644 packages/page-competitive-list/src/WholeNetworkList/useMultisigApprovals.ts create mode 100644 packages/page-competitive-list/src/WholeNetworkList/useProxies.ts create mode 100644 packages/page-competitive-list/src/index.tsx create mode 100644 packages/page-competitive-list/src/md/basic.md create mode 100644 packages/page-competitive-list/src/modals/Backup.tsx create mode 100644 packages/page-competitive-list/src/modals/ChangePass.tsx create mode 100644 packages/page-competitive-list/src/modals/Create.tsx create mode 100644 packages/page-competitive-list/src/modals/CreateConfirmation.tsx create mode 100644 packages/page-competitive-list/src/modals/Delegate.tsx create mode 100644 packages/page-competitive-list/src/modals/Derive.tsx create mode 100644 packages/page-competitive-list/src/modals/ExternalWarning.tsx create mode 100644 packages/page-competitive-list/src/modals/IdentityMain.tsx create mode 100644 packages/page-competitive-list/src/modals/IdentitySub.tsx create mode 100644 packages/page-competitive-list/src/modals/Import.tsx create mode 100644 packages/page-competitive-list/src/modals/InputValidateAmount.tsx create mode 100644 packages/page-competitive-list/src/modals/Ledger.tsx create mode 100644 packages/page-competitive-list/src/modals/MultisigApprove.tsx create mode 100644 packages/page-competitive-list/src/modals/MultisigCreate.tsx create mode 100644 packages/page-competitive-list/src/modals/PasswordInput.tsx create mode 100644 packages/page-competitive-list/src/modals/ProxiedAdd.tsx create mode 100644 packages/page-competitive-list/src/modals/ProxyOverview.tsx create mode 100644 packages/page-competitive-list/src/modals/Qr.tsx create mode 100644 packages/page-competitive-list/src/modals/RecoverAccount.tsx create mode 100644 packages/page-competitive-list/src/modals/RecoverSetup.tsx create mode 100644 packages/page-competitive-list/src/modals/Transfer.tsx create mode 100644 packages/page-competitive-list/src/modals/Undelegate.tsx create mode 100644 packages/page-competitive-list/src/test-support/MemoryStore.ts create mode 100644 packages/page-competitive-list/src/translate.ts create mode 100644 packages/page-competitive-list/src/types.ts create mode 100644 packages/page-competitive-list/src/useCounter.ts create mode 100644 packages/page-competitive-list/src/util.tsx create mode 100644 packages/page-data-statistics/.skip-build create mode 100644 packages/page-data-statistics/.skip-npm create mode 100644 packages/page-data-statistics/LICENSE create mode 100644 packages/page-data-statistics/README.md create mode 100644 packages/page-data-statistics/package.json create mode 100644 packages/page-data-statistics/src/Accounts/Account.tsx create mode 100644 packages/page-data-statistics/src/Accounts/Banner.tsx create mode 100644 packages/page-data-statistics/src/Accounts/BannerClaims.tsx create mode 100644 packages/page-data-statistics/src/Accounts/BannerExtension.tsx create mode 100644 packages/page-data-statistics/src/Accounts/index.tsx create mode 100644 packages/page-data-statistics/src/Accounts/types.ts create mode 100644 packages/page-data-statistics/src/Accounts/useKnownAddresses.ts create mode 100644 packages/page-data-statistics/src/Accounts/useMultisigApprovals.ts create mode 100644 packages/page-data-statistics/src/Accounts/useProxies.ts create mode 100644 packages/page-data-statistics/src/CreateAccount.slow.spec.tsx create mode 100644 packages/page-data-statistics/src/KptRedemption/Account.tsx create mode 100644 packages/page-data-statistics/src/KptRedemption/Banner.tsx create mode 100644 packages/page-data-statistics/src/KptRedemption/BannerClaims.tsx create mode 100644 packages/page-data-statistics/src/KptRedemption/BannerExtension.tsx create mode 100644 packages/page-data-statistics/src/KptRedemption/index.tsx create mode 100644 packages/page-data-statistics/src/KptRedemption/types.ts create mode 100644 packages/page-data-statistics/src/KptRedemption/useKnownAddresses.ts create mode 100644 packages/page-data-statistics/src/KptRedemption/useMultisigApprovals.ts create mode 100644 packages/page-data-statistics/src/KptRedemption/useProxies.ts create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/Account.tsx create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/Banner.tsx create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/BannerClaims.tsx create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/BannerExtension.tsx create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/index.tsx create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/types.ts create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/useKnownAddresses.ts create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/useMultisigApprovals.ts create mode 100644 packages/page-data-statistics/src/ModelKptIssuance/useProxies.ts create mode 100644 packages/page-data-statistics/src/Sidebar/Balances.tsx create mode 100644 packages/page-data-statistics/src/Sidebar/Flags.tsx create mode 100644 packages/page-data-statistics/src/Sidebar/Identity.tsx create mode 100644 packages/page-data-statistics/src/Sidebar/Multisig.tsx create mode 100644 packages/page-data-statistics/src/Sidebar/RegistrarJudgement.tsx create mode 100644 packages/page-data-statistics/src/Sidebar/Sidebar.tsx create mode 100644 packages/page-data-statistics/src/Sidebar/index.tsx create mode 100644 packages/page-data-statistics/src/index.tsx create mode 100644 packages/page-data-statistics/src/md/basic.md create mode 100644 packages/page-data-statistics/src/modals/Backup.tsx create mode 100644 packages/page-data-statistics/src/modals/ChangePass.tsx create mode 100644 packages/page-data-statistics/src/modals/Create.tsx create mode 100644 packages/page-data-statistics/src/modals/CreateConfirmation.tsx create mode 100644 packages/page-data-statistics/src/modals/Delegate.tsx create mode 100644 packages/page-data-statistics/src/modals/Derive.tsx create mode 100644 packages/page-data-statistics/src/modals/ExternalWarning.tsx create mode 100644 packages/page-data-statistics/src/modals/IdentityMain.tsx create mode 100644 packages/page-data-statistics/src/modals/IdentitySub.tsx create mode 100644 packages/page-data-statistics/src/modals/Import.tsx create mode 100644 packages/page-data-statistics/src/modals/InputValidateAmount.tsx create mode 100644 packages/page-data-statistics/src/modals/Ledger.tsx create mode 100644 packages/page-data-statistics/src/modals/MultisigApprove.tsx create mode 100644 packages/page-data-statistics/src/modals/MultisigCreate.tsx create mode 100644 packages/page-data-statistics/src/modals/PasswordInput.tsx create mode 100644 packages/page-data-statistics/src/modals/ProxiedAdd.tsx create mode 100644 packages/page-data-statistics/src/modals/ProxyOverview.tsx create mode 100644 packages/page-data-statistics/src/modals/Qr.tsx create mode 100644 packages/page-data-statistics/src/modals/RecoverAccount.tsx create mode 100644 packages/page-data-statistics/src/modals/RecoverSetup.tsx create mode 100644 packages/page-data-statistics/src/modals/Transfer.tsx create mode 100644 packages/page-data-statistics/src/modals/Undelegate.tsx create mode 100644 packages/page-data-statistics/src/test-support/MemoryStore.ts create mode 100644 packages/page-data-statistics/src/translate.ts create mode 100644 packages/page-data-statistics/src/types.ts create mode 100644 packages/page-data-statistics/src/useCounter.ts create mode 100644 packages/page-data-statistics/src/util.tsx create mode 100644 packages/page-issuance-fund/.skip-build create mode 100644 packages/page-issuance-fund/.skip-npm create mode 100644 packages/page-issuance-fund/LICENSE create mode 100644 packages/page-issuance-fund/README.md create mode 100644 packages/page-issuance-fund/package.json create mode 100644 packages/page-issuance-fund/src/FinancingFund/Council.tsx create mode 100644 packages/page-issuance-fund/src/FinancingFund/Proposal.tsx create mode 100644 packages/page-issuance-fund/src/FinancingFund/ProposalCreate.tsx create mode 100644 packages/page-issuance-fund/src/FinancingFund/Proposals.tsx create mode 100644 packages/page-issuance-fund/src/FinancingFund/Summary.tsx create mode 100644 packages/page-issuance-fund/src/FinancingFund/index.tsx create mode 100644 packages/page-issuance-fund/src/ModelFund/Council.tsx create mode 100644 packages/page-issuance-fund/src/ModelFund/Proposal.tsx create mode 100644 packages/page-issuance-fund/src/ModelFund/ProposalCreate.tsx create mode 100644 packages/page-issuance-fund/src/ModelFund/Proposals.tsx create mode 100644 packages/page-issuance-fund/src/ModelFund/Summary.tsx create mode 100644 packages/page-issuance-fund/src/ModelFund/Summary2.tsx create mode 100644 packages/page-issuance-fund/src/ModelFund/index.tsx create mode 100644 packages/page-issuance-fund/src/TechnologyDevelopmentManagementFund/Council.tsx create mode 100644 packages/page-issuance-fund/src/TechnologyDevelopmentManagementFund/Proposal.tsx create mode 100644 packages/page-issuance-fund/src/TechnologyDevelopmentManagementFund/ProposalCreate.tsx create mode 100644 packages/page-issuance-fund/src/TechnologyDevelopmentManagementFund/Proposals.tsx create mode 100644 packages/page-issuance-fund/src/TechnologyDevelopmentManagementFund/Summary.tsx create mode 100644 packages/page-issuance-fund/src/TechnologyDevelopmentManagementFund/index.tsx create mode 100644 packages/page-issuance-fund/src/Tips/Tip.tsx create mode 100644 packages/page-issuance-fund/src/Tips/TipCreate.tsx create mode 100644 packages/page-issuance-fund/src/Tips/TipEndorse.tsx create mode 100644 packages/page-issuance-fund/src/Tips/TipReason.tsx create mode 100644 packages/page-issuance-fund/src/Tips/Tips.tsx create mode 100644 packages/page-issuance-fund/src/Tips/index.tsx create mode 100644 packages/page-issuance-fund/src/index.tsx create mode 100644 packages/page-issuance-fund/src/md/basic.md create mode 100644 packages/page-issuance-fund/src/translate.ts create mode 100644 packages/page-issuance-fund/src/useCounter.ts create mode 100644 packages/page-knowledge-power/src/Accounts/Account.tsx create mode 100644 packages/page-knowledge-power/src/Accounts/Banner.tsx create mode 100644 packages/page-knowledge-power/src/Accounts/BannerClaims.tsx create mode 100644 packages/page-knowledge-power/src/Accounts/BannerExtension.tsx create mode 100644 packages/page-knowledge-power/src/Accounts/index.tsx create mode 100644 packages/page-knowledge-power/src/Accounts/types.ts create mode 100644 packages/page-knowledge-power/src/Accounts/useKnownAddresses.ts create mode 100644 packages/page-knowledge-power/src/Accounts/useMultisigApprovals.ts create mode 100644 packages/page-knowledge-power/src/Accounts/useProxies.ts create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/Account.tsx create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/Banner.tsx create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/BannerClaims.tsx create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/BannerExtension.tsx create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/index.tsx create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/types.ts create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/useKnownAddresses.ts create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/useMultisigApprovals.ts create mode 100644 packages/page-knowledge-power/src/CompetitiveReviewParticipation/useProxies.ts create mode 100644 packages/page-knowledge-power/src/CreateAccount.slow.spec.tsx create mode 100644 packages/page-knowledge-power/src/DocumentKP/Account.tsx create mode 100644 packages/page-knowledge-power/src/DocumentKP/Banner.tsx create mode 100644 packages/page-knowledge-power/src/DocumentKP/BannerClaims.tsx create mode 100644 packages/page-knowledge-power/src/DocumentKP/BannerExtension.tsx create mode 100644 packages/page-knowledge-power/src/DocumentKP/index.tsx create mode 100644 packages/page-knowledge-power/src/DocumentKP/types.ts create mode 100644 packages/page-knowledge-power/src/DocumentKP/useKnownAddresses.ts create mode 100644 packages/page-knowledge-power/src/DocumentKP/useMultisigApprovals.ts create mode 100644 packages/page-knowledge-power/src/DocumentKP/useProxies.ts create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/Account.tsx create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/Banner.tsx create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/BannerClaims.tsx create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/BannerExtension.tsx create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/index.tsx create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/types.ts create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/useKnownAddresses.ts create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/useMultisigApprovals.ts create mode 100644 packages/page-knowledge-power/src/ExpErienceGoodsKP/useProxies.ts create mode 100644 packages/page-knowledge-power/src/Vanity/Match.tsx create mode 100644 packages/page-knowledge-power/src/Vanity/bipWorker.ts create mode 100644 packages/page-knowledge-power/src/Vanity/index.tsx create mode 100644 packages/page-knowledge-power/src/modals/Backup.tsx create mode 100644 packages/page-knowledge-power/src/modals/ChangePass.tsx create mode 100644 packages/page-knowledge-power/src/modals/Create.tsx create mode 100644 packages/page-knowledge-power/src/modals/CreateConfirmation.tsx create mode 100644 packages/page-knowledge-power/src/modals/Delegate.tsx create mode 100644 packages/page-knowledge-power/src/modals/Derive.tsx create mode 100644 packages/page-knowledge-power/src/modals/ExternalWarning.tsx create mode 100644 packages/page-knowledge-power/src/modals/IdentityMain.tsx create mode 100644 packages/page-knowledge-power/src/modals/IdentitySub.tsx create mode 100644 packages/page-knowledge-power/src/modals/Import.tsx create mode 100644 packages/page-knowledge-power/src/modals/InputValidateAmount.tsx create mode 100644 packages/page-knowledge-power/src/modals/Ledger.tsx create mode 100644 packages/page-knowledge-power/src/modals/MultisigApprove.tsx create mode 100644 packages/page-knowledge-power/src/modals/MultisigCreate.tsx create mode 100644 packages/page-knowledge-power/src/modals/PasswordInput.tsx create mode 100644 packages/page-knowledge-power/src/modals/ProxiedAdd.tsx create mode 100644 packages/page-knowledge-power/src/modals/ProxyOverview.tsx create mode 100644 packages/page-knowledge-power/src/modals/Qr.tsx create mode 100644 packages/page-knowledge-power/src/modals/RecoverAccount.tsx create mode 100644 packages/page-knowledge-power/src/modals/RecoverSetup.tsx create mode 100644 packages/page-knowledge-power/src/modals/Transfer.tsx create mode 100644 packages/page-knowledge-power/src/modals/Undelegate.tsx create mode 100644 packages/page-roles-and-role-groups/.skip-build create mode 100644 packages/page-roles-and-role-groups/.skip-npm create mode 100644 packages/page-roles-and-role-groups/LICENSE create mode 100644 packages/page-roles-and-role-groups/README.md create mode 100644 packages/page-roles-and-role-groups/package.json create mode 100644 packages/page-roles-and-role-groups/src/Accounts/Account.tsx create mode 100644 packages/page-roles-and-role-groups/src/Accounts/Banner.tsx create mode 100644 packages/page-roles-and-role-groups/src/Accounts/BannerClaims.tsx create mode 100644 packages/page-roles-and-role-groups/src/Accounts/BannerExtension.tsx create mode 100644 packages/page-roles-and-role-groups/src/Accounts/index.tsx create mode 100644 packages/page-roles-and-role-groups/src/Accounts/types.ts create mode 100644 packages/page-roles-and-role-groups/src/Accounts/useKnownAddresses.ts create mode 100644 packages/page-roles-and-role-groups/src/Accounts/useMultisigApprovals.ts create mode 100644 packages/page-roles-and-role-groups/src/Accounts/useProxies.ts create mode 100644 packages/page-roles-and-role-groups/src/CreateAccount.slow.spec.tsx create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/Account.tsx create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/Banner.tsx create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/BannerClaims.tsx create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/BannerExtension.tsx create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/index.tsx create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/types.ts create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/useKnownAddresses.ts create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/useMultisigApprovals.ts create mode 100644 packages/page-roles-and-role-groups/src/RoleGroups/useProxies.ts create mode 100644 packages/page-roles-and-role-groups/src/Roles/Account.tsx create mode 100644 packages/page-roles-and-role-groups/src/Roles/Banner.tsx create mode 100644 packages/page-roles-and-role-groups/src/Roles/BannerClaims.tsx create mode 100644 packages/page-roles-and-role-groups/src/Roles/BannerExtension.tsx create mode 100644 packages/page-roles-and-role-groups/src/Roles/index.tsx create mode 100644 packages/page-roles-and-role-groups/src/Roles/types.ts create mode 100644 packages/page-roles-and-role-groups/src/Roles/useKnownAddresses.ts create mode 100644 packages/page-roles-and-role-groups/src/Roles/useMultisigApprovals.ts create mode 100644 packages/page-roles-and-role-groups/src/Roles/useProxies.ts create mode 100644 packages/page-roles-and-role-groups/src/Sidebar/Balances.tsx create mode 100644 packages/page-roles-and-role-groups/src/Sidebar/Flags.tsx create mode 100644 packages/page-roles-and-role-groups/src/Sidebar/Identity.tsx create mode 100644 packages/page-roles-and-role-groups/src/Sidebar/Multisig.tsx create mode 100644 packages/page-roles-and-role-groups/src/Sidebar/RegistrarJudgement.tsx create mode 100644 packages/page-roles-and-role-groups/src/Sidebar/Sidebar.tsx create mode 100644 packages/page-roles-and-role-groups/src/Sidebar/index.tsx create mode 100644 packages/page-roles-and-role-groups/src/index.tsx create mode 100644 packages/page-roles-and-role-groups/src/md/basic.md create mode 100644 packages/page-roles-and-role-groups/src/modals/Backup.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/ChangePass.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/Create.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/CreateConfirmation.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/Delegate.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/Derive.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/ExternalWarning.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/IdentityMain.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/IdentitySub.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/Import.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/InputValidateAmount.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/Ledger.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/MultisigApprove.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/MultisigCreate.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/PasswordInput.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/ProxiedAdd.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/ProxyOverview.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/Qr.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/RecoverAccount.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/RecoverSetup.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/Transfer.tsx create mode 100644 packages/page-roles-and-role-groups/src/modals/Undelegate.tsx create mode 100644 packages/page-roles-and-role-groups/src/test-support/MemoryStore.ts create mode 100644 packages/page-roles-and-role-groups/src/translate.ts create mode 100644 packages/page-roles-and-role-groups/src/types.ts create mode 100644 packages/page-roles-and-role-groups/src/useCounter.ts create mode 100644 packages/page-roles-and-role-groups/src/util.tsx diff --git a/packages/apps-routing/src/chain_application.ts b/packages/apps-routing/src/chain_application1.ts similarity index 89% rename from packages/apps-routing/src/chain_application.ts rename to packages/apps-routing/src/chain_application1.ts index 8ee6f9f4f29b..4ac25ccc4ed0 100644 --- a/packages/apps-routing/src/chain_application.ts +++ b/packages/apps-routing/src/chain_application1.ts @@ -9,11 +9,13 @@ import Component, { useCounter } from '@polkadot/app-settings'; export default function create (t: TFunction): Route { return { Component, - display: {}, + display: { + needsApi: [] + }, group: 'chain_application', icon: 'browser', name: 'chain_application', text: t('nav.chain_application', 'Chain Application', { ns: 'apps-routing' }), - useCounter + //useCounter }; } diff --git a/packages/apps-routing/src/competitive_list.ts b/packages/apps-routing/src/competitive_list.ts index 70e5e9addcc2..69bf737351cf 100644 --- a/packages/apps-routing/src/competitive_list.ts +++ b/packages/apps-routing/src/competitive_list.ts @@ -4,7 +4,7 @@ import { TFunction } from 'i18next'; import { Route } from './types'; -import Component, { useCounter } from '@polkadot/app-settings'; +import Component, { useCounter } from '@ctt/app-competitive-list'; export default function create (t: TFunction): Route { return { diff --git a/packages/apps-routing/src/data_statistics.ts b/packages/apps-routing/src/data_statistics.ts index 52a60cd272e1..1f82d5ef3b1f 100644 --- a/packages/apps-routing/src/data_statistics.ts +++ b/packages/apps-routing/src/data_statistics.ts @@ -4,7 +4,7 @@ import { TFunction } from 'i18next'; import { Route } from './types'; -import Component, { useCounter } from '@polkadot/app-settings'; +import Component, { useCounter } from '@ctt/app-data-statistics'; export default function create (t: TFunction): Route { return { diff --git a/packages/apps-routing/src/issuance_fund.ts b/packages/apps-routing/src/issuance_fund.ts index 37baf873cd27..7eb39a6c3e4c 100644 --- a/packages/apps-routing/src/issuance_fund.ts +++ b/packages/apps-routing/src/issuance_fund.ts @@ -4,7 +4,7 @@ import { TFunction } from 'i18next'; import { Route } from './types'; -import Component, { useCounter } from '@polkadot/app-treasury'; +import Component, { useCounter } from '@polkadot/app-issuance-fund'; export default function create (t: TFunction): Route { return { diff --git a/packages/apps-routing/src/knowledge_power.ts b/packages/apps-routing/src/knowledge_power.ts index 74153ffe3d94..48c3f792d5e4 100644 --- a/packages/apps-routing/src/knowledge_power.ts +++ b/packages/apps-routing/src/knowledge_power.ts @@ -9,13 +9,18 @@ import Component, { useCounter } from '@ctt/app-knowledge-power'; export default function create (t: TFunction): Route { return { Component, + /* display: {}, + group: 'knowledge_power', + icon: 'lightbulb-on', + name: 'knowledge_power', + */ display: { needsApi: [] }, group: 'chain_application', - icon: 'lightbulb-on', + icon: 'database', name: 'knowledge_power', text: t('nav.knowledge_power', 'Knowledge Power', { ns: 'apps-routing' }), - /* useCounter */ + /* useCounter */ }; } diff --git a/packages/apps-routing/src/roles_and_role_groups.ts b/packages/apps-routing/src/roles_and_role_groups.ts index 8aa60ee47507..387e5fb6358b 100644 --- a/packages/apps-routing/src/roles_and_role_groups.ts +++ b/packages/apps-routing/src/roles_and_role_groups.ts @@ -4,7 +4,7 @@ import { TFunction } from 'i18next'; import { Route } from './types'; -import Component, { useCounter } from '@polkadot/app-settings'; +import Component, { useCounter } from '@ctt/app-roles-and-role-groups'; export default function create (t: TFunction): Route { return { diff --git a/packages/apps/public/locales/zh/translation.json b/packages/apps/public/locales/zh/translation.json index ec54fc2c1291..7be1162f158f 100644 --- a/packages/apps/public/locales/zh/translation.json +++ b/packages/apps/public/locales/zh/translation.json @@ -29,6 +29,7 @@ "ABI": "ABI", "Acceptance of new members and bids": "接受新成员和投标", "Acceptance proposal to council": "向理事会提交的同意议案", + "Account KP":"帐户知识算力", "Account actions": "账户操作", "Account balance:": "账户余额", "Accounts": "账户", @@ -65,10 +66,14 @@ "Amount to delegate for any democracy vote. This is adjusted using the available funds on the account.": "相当于代表任何民主投票。使用账户上的可用资金进行调整。", "An URL that is linked to this identity.": "链接到这个身份的URL。", "An encrypted backup file will be created once you have pressed the \"Download\" button. This can be used to re-import your account on any other machine.": "一旦你按下 \"下载\" 按钮,就会创建一个加密的备份文件。这样你可以在另外任何一台设备上重新导入你的账户。", + "Annual additional issuance (kpt)":"年度增发数(kpt)", + "Annual issue":"年度发行(kpt)", + "Annual model creation":"年度模型创建数", "Any account can request payout for stakers, this is not limited to accounts that will be rewarded.": "任何帐户都可以为质押者申请付款,这不限于任何将得到奖励的帐户。", "Any account set as proxy will be able to perform actions in place of the proxied account": "任何设置为代理的帐户都可以代替代理帐户执行操作", "Any combination of the four options may be approved of by the voter. There is no need to select only one option!": "这四种选择的任何组合都可能得到选民的认可。不需要只选择一个选项!", "Any democracy vote performed by the delegated account will result in an additional vote from the delegating account": "任何由授权帐户进行的民主投票都将导致来自授权帐户的额外投票", + "AppId":"应用id", "Application of slashes from era {{id}}": "era {{id}}中惩罚的应用", "Apply to UI": "应用于 UI", "Approvals": "赞同", @@ -108,6 +113,7 @@ "Cancel slashes": "取消惩罚", "Cancel this call hash": "取消此调用哈希", "Chain info": "链信息", + "Chain Application": "链应用", "Chain specifications": "生成含网络参数的二维码", "Change": "更改", "Change account password": "更改账户密码", @@ -133,12 +139,15 @@ "Color": "颜色", "Committee prime member, default voting": "委员会主要成员,默认投票信念值", "Completed": "完成", + "Competitive review participation":"竞争性点评参与度", "Confirm ABI removal": "确认 ABI 清除", "Confirm account removal": "确定账户被移除", "Confirm address removal": "确定地址被移除", "Confirm claim": "确定认领", "Confirm code removal": "确定代码被移除", "Confirm contract removal": "确定合约被移除", + "Confiscated":"罚没数", + "Confiscation (kp)":"算力罚没(kp)", "Consider storing your account in a signer such as a browser extension, hardware device, QR-capable phone wallet (non-connected) or desktop application for optimal account security.": "考虑将您的帐户存储在签名者(如浏览器扩展、硬件设备、支持 QR 功能的电话钱包(非连接)或桌面应用程序中,以获得最佳的帐户安全性。", "Constants": "常数", "Continue": "继续", @@ -178,13 +187,17 @@ "Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use \"ed25519\".": "确定将使用哪种加密方法创建此帐户。注意,要在Polkadot上进行验证,会话账户必须使用 \"ed25519\"。", "Developer": "开发者", "Development": "开发", + "Development type":"开发类型", "Dismiss all notifications": "禁用所有通知", "Dispatch": "发送信息", "Display overview information for the selected validator, including blocks produced.": "显示所选验证人的概述信息,包括生成的块。", "Distinct stash and controller accounts are recommended to ensure fund security. You will be allowed to make the transaction, but take care to not tie up all funds, only use a portion of the available funds during this period.": "为确保资金安全,建议使用不同的储存账户和控制账户。您可以进行交易,但要注意不要占用所有资金,只在这段时间内使用部分可用资金。", "Do not include a tip for the block author": "不包含区块打包者小费", + "Document id":"软文id", + "Document KP":"软文知识算力", "Don't use a proxy for this call": "不要对此调用使用代理", "Download": "下载", + "Effective balance (KPT)":"有效余额(KPT)", "Either approve or reject this call.": "赞成或者拒绝此调用。", "Election of new council candidates": "选举新的理事会候选人", "Enactment of the result of referendum {{id}}": "公投结果{{id}}的执行", @@ -198,13 +211,19 @@ "Enter the Asset ID of the token you want to transfer.": "输入要转账的代币的资产 ID。", "Erroneous": "错误的", "Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)": "在 {{elapsed}}秒,生成 {{count}} 个 秘钥 (平均{{avg}}个秘钥/秒)", + "Exchange rate":"兑换率", "Execute anonymous scheduled task": "执行匿名计划任务", "Execute named scheduled task {{id}}": "执行命名的计划任务 {{id}}", + "Experience goods":"体验商品", + "Experience goods id":"体验商品id", + "Experience goods num":"体验商品数", "Extensions": "浏览器插件", "External": "外部的", + "Experience goods KP":"体验商品知识算力", "Extrinsic submission": "外部的提交", "Fast track": "快速通道", "Fast track proposal": "快速通道的提案", + "Financing funde":"融资基金", "Filter available candidates based on name, address or short account index.": "根据姓名、地址或短帐户索引筛选可用的候选人。", "For approvals outstanding approvers will be shown, for hashes that should be cancelled the first approver is required.": "对于审批,将显示未完成的审批人,对于应取消的哈希,需要第一个审批人。", "For final approvals, the actual full call data is required to execute the transaction": "对于最终批准,执行交易需要实际的完整调用数据", @@ -216,12 +235,15 @@ "Forget this code hash": "忘记这个代码哈希", "Forget this contract": "忘记该合约", "Forks": "(Forks)分叉", + "Function type":"功能类型", "Full Legal Name": "法定全称", "Future versions of the web-only interface will drop support for non-external accounts, much like the IPFS version.": "未来版本的纯 web 界面将不再支持非外部帐户,这与 IPFS 版本非常相似。", "General": "通用", "Generate {{lng}}/translation.json": "生成 {{lng}}/translation.json", "Genesis Hash": "创世区块哈希", "Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched": "创世区块哈希代表网络的初始状态,在网络启动后不可改变", + "Goods model list":"商品模型榜单", + "Goods type id":"商品类型id", "Governance": "治理", "Grandpa": "Grandpa", "Hash data": "哈希数据", @@ -247,10 +269,14 @@ "Injected": "已插入", "It is recommended that you create/store your accounts securely and externally from the app. On {{yourBrowser}} the following browser extensions are available for use -": "强烈建议你在本应用的外部安全地创建/存储你的账户。在 {{yourBrowser}} 可使用下面的浏览器扩展 -", "Judge": "判断", + "KPT issuance year":"kpt增发年度", + "KPT redemption":"kpt赎回", "Keys from rotateKeys": "来自 rotateKeys 的密钥", "Known good": "已知良好", + "Knowledge power (kp)":"知识算力(kp)", "Learn more...": "了解更多...", "Lifetime (# of blocks)": "使用期限 (# of blocks)", + "List period":"榜单期数", "Loading": "加载中", "Locked funds (e.g. for staking) are counted.": "已锁定的资金(例如用于 staking 的资金)将被计算在内。", "Locked1x": "锁定1X", @@ -265,6 +291,7 @@ "Manage proxies": "管理代理", "Manage your connection to Ledger S": "管理你的连接到 Ledger S", "Max, {{eras}} eras": "最大, {{eras}} eras", + "Maximum review cost (rmb)":"最大点评成本(rmb)", "Median tip selected": "选择中间数小费", "Message data": "消息数据", "Message origin.": "消息来源", @@ -272,6 +299,8 @@ "Metadata": "元数据", "Metadata {{count}}": "元数据 {{count}}", "Mnemonic": "助记词", + "Model fund":"模型基金", + "Model kpt issuance":"模型kpt增发", "Most profitable": "最有利可图", "Most recent head data": "最近的头数据", "Motions": "Motions", @@ -360,6 +389,10 @@ "Normal transfer without keep-alive checks": "没有保持活跃检查的正常转账", "Not updated in the last block": "在最后一个块中未更新", "Nothing queued for execution": "没有排队等待中的执行", + "Number of additional issuances (KPT)":"增发数(KPT)", + "Number of financing kpts":"融资kpt数", + "Number of positive trends":"正趋势次数", + "Number of awards":"奖励数(kpt)", "Of the beneficiary amount, at least {{bondPercentage}} would need to be put up as collateral. The maximum of this and the minimum bond will be used to secure the proposal, refundable if it passes.": "在受益人金额中,至少{{bondPercentage}} 需要作为抵押品。最高保证金和最低保证金将用于担保该提案,如果通过,可退还。", "Once bonded, it wil need to be unlocked/withdrawn and will be locked for at least the bonding duration.": "一旦绑定,则需要解锁/取出,并至少在绑定时间内锁定", "Once transferred the amount will become available on the chain for use.": "一旦进行了转账,该金额将可在链上使用。", @@ -378,8 +411,10 @@ "Owned": "已拥有", "Parachains": "平行链", "Parachains overview": "平行链概览", + "pass":"通过", "Paste here the address of the contact you want to add to your address book.": "把你想要添加到地址簿的联系人地址粘贴到这里。", "Paste the signed message into the field below. The placeholder text is there as a hint to what the message should look like:": "将签名的消息粘贴到下面的字段中。占位符文本是一个提示,它看起来是这样的:", + "Payment number":"支付号", "Payout": "支付", "Payout all": "支付所有", "Payout all stakers": "支付所有的质押者", @@ -400,6 +435,7 @@ "Present the QR code containing the signature to the UI. Once scanned it will be submitted for on-chain processing and execution.": "将包含签名的二维码呈现给 UI。一旦扫描,它将提交到链上处理和执行。", "Produced blocks": "生产区块", "Proposal can either be to approve or reject this spend. One approved, the change is applied by either removing the proposal or scheduling payout.": "提案可以是批准或拒绝这一支出。如果得到批准,变更可以通过删除提议或安排支付来实施。", + "Proposal":"提案原像", "Proposals": "提案", "Proposals ({{count}})": "提案({{count}})", "Propose": "提出", @@ -416,11 +452,14 @@ "Proxy overview": "代理概览", "RPC calls": "RPC 调用", "Random": "随机生成颜色", + "Ranking":"榜单名次", "Raw seed": "原始种子", "Raw storage": "原始存储", "Reasonable": "合理的", "Reassign": "重新分配", "Redeem": "赎回", + "Redeemable (KPT)":"可赎回数(KPT)", + "Redemption type":"赎回类型", "Register": "注册", "Register Asset": "注册资产", "Register a parachain": "注册一个平行链", @@ -435,6 +474,8 @@ "Remove item": "移除项目", "Remove sub": "移除子账户", "Renomination required ({{count}})": "重新提名 ({{count}})", + "Review winner":"点评中签者", + "Reviews":"点评次数", "Reset": "重置", "Restore": "恢复", "Restore JSON": "恢复JSON", @@ -447,6 +488,9 @@ "Revert": "回复", "Revert pending slashes": "回复待定惩罚", "Rewards (once paid) can be deposited to either the stash or controller, with different effects.": "奖励(一旦支付)可以存入储物或控制账户,但效果不同。", + "Reward object":"奖励对象", + "Role Groups Name":"角色组名称", + "Sales":"销售额", "Save": "保存", "Save & Reload": "保存并重载", "Save delegation": "保存代表", @@ -539,6 +583,7 @@ "Start of next membership challenge period": "下一个会员挑战期开始", "Start of next spending period": "下一个花费期的开始", "Start of the next referendum voting period": "下一次公投投票期开始", + "Start up period": "启动期", "Start recovery": "开始恢复", "Stash": "存储账户", "Stop": "停止", @@ -565,11 +610,13 @@ "Sudo access": "sudo接入", "Sudo key": "Sudo 密钥", "Supply a backed-up JSON file, encrypted with your account-specific password.": "提供一个备份的JSON文件,用你的账户密码来加密。", + "Surplus (kpt)": "剩余(kpt)", "Swap to a non-executing approval type, with subsequent calls providing the actual call data.": "交换到非执行的批准类型,后续调用提供实际调用数据。", "Switch": "转换", "Targets": "目标", "Tech. committee": "技术委员会", "Technical committee": "技术委员会", + "Technology Development Management Fund":"技术开发管理基金", "Test account": "测试账户", "The ABI for the WASM code. In this step it is optional, but setting it here simplifies the setup of contract instances.": "用于WASM代码的ABI。在这个步骤中,它是可选的,但是在这里设置它简化了合约 实例的设置。", "The ABI for the WASM code. Since we will be making a call into the code, the ABI is required and stored for future operations such as sending messages.": "用于WASM代码的ABI。因为我们将对代码进行调用,所以ABI是必需的,并且存储它用于将来的操作,比如发送消息", @@ -797,6 +844,7 @@ "This pubic key is what will be visible in your queued keys list. It is generated based on the seed and the crypto used.": "这个公共密钥将在您的队列密钥列表中可见。它是基于所使用的种子和密码生成的。", "This vote does not affect any economics of the Polkadot platform. Staking rewards, inflation, effective market capitalisation and the underlying balances of every account remain completely unchanged. It is \"merely\" about what units we use to denominate the balances into \"DOT\" for the purpose of display.": "这次投票不会影响波卡平台的任何经济效益。质押回报、通胀、有效市值和每个账户的基本余额仍完全不变。它“仅仅”是关于我们用什么单位来把余额命名,并以便显示。", "This will apply to any future use of this account as stored on this browser. Ensure that you securely store this new password and that it is strong and unique to the account.": "此帐户将在浏览器中存储以供将来使用。请确保你安全地存储了这个新密码,而且此密码强度足够,且是此账户的唯一密码。", + "Threshold":"阈值", "Tip": "小费", "Tip (optional)": "小费(可选)", "Tippers ({{count}})": "付小费的人({{count}})", @@ -804,6 +852,11 @@ "To council": "对理事会", "To ensure optimal fund security using the same stash/controller is strongly discouraged, but not forbidden.": "为了确保最佳的资金安全使用相同的stash/controller 账户是强烈不建议的,但不是禁止。", "Total amount of fund that will be reserved. These funds are returned when the identity is cleared": "预留资金的总额。当身份被清除时,这些资金将被返还", + "Total annual sales":"年度销售总额(rmb)", + "Total cost of reviews (rmb)":"点评总成本(rmb)", + "Total funds (kpt)": "基金总数(kpt)", + "Total model creation releases":"模型创建发行总数(kpt)", + "Total number of additional issues in model year":"模型年度增发总数(kpt)", "Transfer": "转账", "Transfer an amount from a specific account into a parachain.": "将一笔款项从一个特定的账户转到一个平行链账户", "Transfer to chain": "给链转账", @@ -869,6 +922,7 @@ "We found a pre-claim with this Polkadot address. However, attesting requires signing with this account. To continue with attesting, please add this account as an owned account first.": "我们在波卡的地址找到了一个预先认领。但是,认证需要在这个账户上签字。要继续进行认证,请先将此帐户添加为自有帐户", "We will provide you with a generated backup file after your account is created. As long as you have access to your account you can always download this file later by clicking on \"Backup\" button from the Accounts section.": "创建帐户后,我们将为您提供一个生成的备份文件。只要您有权访问帐户,以后就可以通过单击\"帐户\"中的\"备份\"按钮来下载此文件。", "When submitting a proposal the hash needs to be known. Proposals can be submitted with hash-only, but upon dispatch the preimage needs to be available.": "在提交议案时,需要知道哈希。提案只能通过哈希提交,但在发送时,需要有预图像", + "Whole network list":"全网榜单", "With the Firefox browser connecting to insecure WebSockets ({{wsUrl}}) will fail due to the browser not allowing localhost access from a secure site.": "用Firefox连接不安全的 WebSockets ({{wsUrl}}) 由于浏览器不允许从一个安全的站点访问本地服务, 可能访问失败.", "With the keep-alive option set, the account is protected against removal due to low balances.": "通过设置keep-alive选项,可以保护帐户不因余额低而被删除。", "Withdraw these unbonded funds": "提取这些解绑的资金", @@ -1070,6 +1124,8 @@ "key type to set": "设置key类型", "keypair crypto type": "密钥对加密类型", "kind": "友好的", + "kpt balances": "kpt 余额", + "kp balances": "kp 算力余额", "last #": "最后 #", "last block": "最后的区块", "last reward": "上一次奖励", diff --git a/packages/apps/src/Menu/ChainInfo.tsx b/packages/apps/src/Menu/ChainInfo.tsx index 4ae91b6db6e7..6725019fab57 100644 --- a/packages/apps/src/Menu/ChainInfo.tsx +++ b/packages/apps/src/Menu/ChainInfo.tsx @@ -33,9 +33,11 @@ function ChainInfo ({ className }: Props): React.ReactElement {
+ {/*runtimeVersion && (
{t('version {{version}}', { replace: { version: runtimeVersion.specVersion.toNumber() } })}
)*/} + { const { api, isApiReady } = useApi(); - return (
{isApiReady && ( diff --git a/packages/apps/src/Menu/index.tsx b/packages/apps/src/Menu/index.tsx index ae8fb673a50a..9bf1c46c0b1a 100644 --- a/packages/apps/src/Menu/index.tsx +++ b/packages/apps/src/Menu/index.tsx @@ -31,7 +31,7 @@ const disabledLog = new Map(); function createExternals (t: TFunction): ItemRoute[] { return [ { href: 'https://github.com/CTT-block-chain/web-wallet', icon: 'code-branch', name: 'github', text: t('nav.github', 'GitHub', { ns: 'apps-routing' }) }, - /* { href: 'https://wiki.polkadot.network', icon: 'book', name: 'wiki', text: t('nav.wiki', 'Wiki', { ns: 'apps-routing' }) } */ + /* { href: 'https://wiki.polkadot.network', icon: 'book', name: 'wiki', text: t('nav.wiki', 'Wiki', { ns: 'apps-routing' }) } */ ]; } @@ -76,7 +76,6 @@ function extractGroups (routing: Routes, groupNames: Record, api } else { all[route.group].routes.push(route); } - return all; }, {}) ) @@ -101,7 +100,7 @@ function Menu ({ className = '' }: Props): React.ReactElement { developer: t('Developer'), governance: t('Governance'), network: t('Network'), - //knowledge_power: t('Knowledge Power'), + /* knowledge_power: t('Knowledge Power'), */ chain_application: t('Chain Application'), settings: t('Settings') }); diff --git a/packages/page-competitive-list/.skip-build b/packages/page-competitive-list/.skip-build new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/page-competitive-list/.skip-npm b/packages/page-competitive-list/.skip-npm new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/page-competitive-list/LICENSE b/packages/page-competitive-list/LICENSE new file mode 100644 index 000000000000..0d381b2e97dc --- /dev/null +++ b/packages/page-competitive-list/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/packages/page-competitive-list/README.md b/packages/page-competitive-list/README.md new file mode 100644 index 000000000000..75a8b1664007 --- /dev/null +++ b/packages/page-competitive-list/README.md @@ -0,0 +1 @@ +# @polkadot/app-accounts diff --git a/packages/page-competitive-list/package.json b/packages/page-competitive-list/package.json new file mode 100644 index 000000000000..c56f7a9bea4c --- /dev/null +++ b/packages/page-competitive-list/package.json @@ -0,0 +1,22 @@ +{ + "name": "@ctt/app-competitive-list", + "private": true, + "version": "0.66.2-14", + "main": "index.js", + "repository": "github:polkadot-js/apps", + "author": "Jaco Greeff ", + "maintainers": [], + "contributors": [], + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@polkadot/react-components": "^0.66.2-14", + "@polkadot/vanitygen": "^0.23.2-4", + "detect-browser": "^5.2.0", + "file-saver": "^2.0.2" + }, + "devDependencies": { + "@testing-library/react": "^11.1.0", + "testcontainers": "^4.6.0" + } +} diff --git a/packages/page-competitive-list/src/Accounts/Account.tsx b/packages/page-competitive-list/src/Accounts/Account.tsx new file mode 100644 index 000000000000..2b2fa663f7a1 --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/Account.tsx @@ -0,0 +1,436 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { SubmittableExtrinsic } from '@polkadot/api/types'; +import { DeriveBalancesAll, DeriveDemocracyLock } from '@polkadot/api-derive/types'; +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { ThemeDef } from '@polkadot/react-components/types'; +import { ProxyDefinition, RecoveryConfig } from '@polkadot/types/interfaces'; +import { KeyringAddress } from '@polkadot/ui-keyring/types'; +import { Delegation } from '../types'; + +import BN from 'bn.js'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import styled, { ThemeContext } from 'styled-components'; +import { ApiPromise } from '@polkadot/api'; +import { getLedger } from '@polkadot/react-api'; +import { AddressInfo, AddressMini, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, LinkExternal, Menu, Popup, StatusContext, Tags } from '@polkadot/react-components'; +import { useAccountInfo, useApi, useCall, useToggle } from '@polkadot/react-hooks'; +import { Option } from '@polkadot/types'; +import keyring from '@polkadot/ui-keyring'; +import { BN_ZERO, formatBalance, formatNumber } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import { createMenuGroup } from '../util'; +import Backup from '../modals/Backup'; +import ChangePass from '../modals/ChangePass'; +import DelegateModal from '../modals/Delegate'; +import Derive from '../modals/Derive'; +import IdentityMain from '../modals/IdentityMain'; +import IdentitySub from '../modals/IdentitySub'; +import ProxyOverview from '../modals/ProxyOverview'; +import MultisigApprove from '../modals/MultisigApprove'; +import RecoverAccount from '../modals/RecoverAccount'; +import RecoverSetup from '../modals/RecoverSetup'; +import Transfer from '../modals/Transfer'; +import UndelegateModal from '../modals/Undelegate'; +import useMultisigApprovals from './useMultisigApprovals'; +import useProxies from './useProxies'; + +interface Props { + account: KeyringAddress; + className?: string; + delegation?: Delegation; + filter: string; + isFavorite: boolean; + proxy?: [ProxyDefinition[], BN]; + setBalance: (address: string, value: BN) => void; + toggleFavorite: (address: string) => void; +} + +interface DemocracyUnlockable { + democracyUnlockTx: SubmittableExtrinsic<'promise'> | null; + ids: BN[]; +} + +function calcVisible (filter: string, name: string, tags: string[]): boolean { + if (filter.length === 0) { + return true; + } + + const _filter = filter.toLowerCase(); + + return tags.reduce((result: boolean, tag: string): boolean => { + return result || tag.toLowerCase().includes(_filter); + }, name.toLowerCase().includes(_filter)); +} + +function createClearDemocracyTx (api: ApiPromise, address: string, unlockableIds: BN[]): SubmittableExtrinsic<'promise'> { + return api.tx.utility.batch( + unlockableIds + .map((id) => api.tx.democracy.removeVote(id)) + .concat(api.tx.democracy.unlock(address)) + ); +} + +const transformRecovery = { + transform: (opt: Option) => opt.unwrapOr(null) +}; + +function Account ({ account: { address, meta }, className = '', delegation, filter, isFavorite, proxy, setBalance, toggleFavorite }: Props): React.ReactElement | null { + console.log("Account--account:"+address) + const { t } = useTranslation(); + const { theme } = useContext(ThemeContext); + const { queueExtrinsic } = useContext(StatusContext); + const api = useApi(); + const bestNumber = useCall(api.api.derive.chain.bestNumber); + console.log("Account--bestNumber:"+JSON.stringify(useCall(api.api.derive.chain.bestNumber))) + console.log("Account--balances.all:"+JSON.stringify(useCall(api.api.derive.balances.all, [address]))) + const balancesAll = useCall(api.api.derive.balances.all, [address]); + const democracyLocks = useCall(api.api.derive.democracy?.locks, [address]); + const recoveryInfo = useCall(api.api.query.recovery?.recoverable, [address], transformRecovery); + const multiInfos = useMultisigApprovals(address); + const proxyInfo = useProxies(address); + const { flags: { isDevelopment, isExternal, isHardware, isInjected, isMultisig, isProxied }, genesisHash, identity, name: accName, onSetGenesisHash, tags } = useAccountInfo(address); + const [{ democracyUnlockTx }, setUnlockableIds] = useState({ democracyUnlockTx: null, ids: [] }); + const [vestingVestTx, setVestingTx] = useState | null>(null); + const [isBackupOpen, toggleBackup] = useToggle(); + const [isDeriveOpen, toggleDerive] = useToggle(); + const [isForgetOpen, toggleForget] = useToggle(); + const [isIdentityMainOpen, toggleIdentityMain] = useToggle(); + const [isIdentitySubOpen, toggleIdentitySub] = useToggle(); + const [isMultisigOpen, toggleMultisig] = useToggle(); + const [isProxyOverviewOpen, toggleProxyOverview] = useToggle(); + const [isPasswordOpen, togglePassword] = useToggle(); + const [isRecoverAccountOpen, toggleRecoverAccount] = useToggle(); + const [isRecoverSetupOpen, toggleRecoverSetup] = useToggle(); + const [isSettingsOpen, toggleSettings] = useToggle(); + const [isTransferOpen, toggleTransfer] = useToggle(); + const [isDelegateOpen, toggleDelegate] = useToggle(); + const [isUndelegateOpen, toggleUndelegate] = useToggle(); + + useEffect((): void => { + if (balancesAll) { + setBalance(address, balancesAll.freeBalance.add(balancesAll.reservedBalance)); + + api.api.tx.vesting?.vest && setVestingTx(() => + balancesAll.vestingLocked.isZero() + ? null + : api.api.tx.vesting.vest() + ); + } + }, [address, api, balancesAll, setBalance]); + + useEffect((): void => { + bestNumber && democracyLocks && setUnlockableIds( + (prev): DemocracyUnlockable => { + const ids = democracyLocks + .filter(({ isFinished, unlockAt }) => isFinished && bestNumber.gt(unlockAt)) + .map(({ referendumId }) => referendumId); + + if (JSON.stringify(prev.ids) === JSON.stringify(ids)) { + return prev; + } + + return { + democracyUnlockTx: createClearDemocracyTx(api.api, address, ids), + ids + }; + } + ); + }, [address, api, bestNumber, democracyLocks]); + + const isVisible = useMemo( + () => calcVisible(filter, accName, tags), + [accName, filter, tags] + ); + + const _onFavorite = useCallback( + () => toggleFavorite(address), + [address, toggleFavorite] + ); + + const _onForget = useCallback( + (): void => { + if (!address) { + return; + } + + const status: Partial = { + account: address, + action: 'forget' + }; + + try { + keyring.forgetAccount(address); + status.status = 'success'; + status.message = t('account forgotten'); + } catch (error) { + status.status = 'error'; + status.message = (error as Error).message; + } + }, + [address, t] + ); + + const _clearDemocracyLocks = useCallback( + () => democracyUnlockTx && queueExtrinsic({ + accountId: address, + extrinsic: democracyUnlockTx + }), + [address, democracyUnlockTx, queueExtrinsic] + ); + + const _vestingVest = useCallback( + () => vestingVestTx && queueExtrinsic({ + accountId: address, + extrinsic: vestingVestTx + }), + [address, queueExtrinsic, vestingVestTx] + ); + + const _showOnHardware = useCallback( + // TODO: we should check the hardwareType from metadata here as well, + // for now we are always assuming hardwareType === 'ledger' + (): void => { + getLedger() + .getAddress(true, meta.accountOffset as number || 0, meta.addressOffset as number || 0) + .catch((error): void => { + console.error(`ledger: ${(error as Error).message}`); + }); + }, + [meta] + ); + + if (!isVisible) { + return null; + } + const testValue1=22; + const testValue2=33; + return ( + + + + + + {recoveryInfo && ( + +

{t('This account is recoverable, with the following friends:')}

+
+ {recoveryInfo.friends.map((friend, index): React.ReactNode => ( + + ))} +
+ + + + + + + + + + + + + + + +
{t('threshold')}{formatNumber(recoveryInfo.threshold)}
{t('delay')}{formatNumber(recoveryInfo.delayPeriod)}
{t('deposit')}{formatBalance(recoveryInfo.deposit)}
+
+ } + icon='shield' + /> + )} + {multiInfos && multiInfos.length !== 0 && ( + ('Multisig approvals pending')} + info={multiInfos.length} + /> + )} + {isProxied && !proxyInfo.hasOwned && ( + ('Proxied account has no owned proxies')} + info='0' + /> + )} + {delegation?.accountDelegated && ( + ('This account has a governance delegation')} + icon='calendar-check' + onClick={toggleDelegate} + /> + )} + {!!proxy?.[0].length && ( + ('This account has {{proxyNumber}} proxy set.', { + replace: { + proxyNumber: proxy[0].length + } + })} + icon='arrow-right' + onClick={toggleProxyOverview} + /> + )} + + + + {isBackupOpen && ( + + )} + {isDelegateOpen && ( + + )} + {isDeriveOpen && ( + + )} + {isForgetOpen && ( + + )} + {isIdentityMainOpen && ( + + )} + {isIdentitySubOpen && ( + + )} + {isPasswordOpen && ( + + )} + {isTransferOpen && ( + + )} + {isProxyOverviewOpen && ( + + )} + {isMultisigOpen && multiInfos && ( + + )} + {isRecoverAccountOpen && ( + + )} + {isRecoverSetupOpen && ( + + )} + {isUndelegateOpen && ( + + )} + + + + + + +
+ +
+ + + {balancesAll?.accountNonce.gt(BN_ZERO) && formatNumber(balancesAll.accountNonce)} + + + {testValue1} + + + {testValue2} + + + + + + + + + + ); +} + +export default React.memo(styled(Account)` + .tags { + width: 100%; + min-height: 1.5rem; + } +`); diff --git a/packages/page-competitive-list/src/Accounts/Banner.tsx b/packages/page-competitive-list/src/Accounts/Banner.tsx new file mode 100644 index 000000000000..5e5216f16a58 --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/Banner.tsx @@ -0,0 +1,27 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import styled from 'styled-components'; + +interface Props { + children: React.ReactNode; + className?: string; + type: 'warning' | 'error'; +} + +function Banner ({ children, className = '', type }: Props): React.ReactElement | null { + return ( +
+
+ {children} +
+
+ ); +} + +export default React.memo(styled(Banner)` + .box { + padding: 0 0.5rem; + } +`); diff --git a/packages/page-competitive-list/src/Accounts/BannerClaims.tsx b/packages/page-competitive-list/src/Accounts/BannerClaims.tsx new file mode 100644 index 000000000000..25fc546e5002 --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/BannerClaims.tsx @@ -0,0 +1,25 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import useClaimCounter from '@polkadot/app-claims/useCounter'; // exceptionally CRAP idea + +import { useTranslation } from '../translate'; +import Banner from './Banner'; + +function BannerExtension (): React.ReactElement | null { + const claimCount = useClaimCounter(); + const { t } = useTranslation(); + + if (!claimCount) { + return null; + } + + return ( + +

{t('You have {{claimCount}} accounts that need attestations. Use the Claim Tokens app on the navigation bar to complete the process. Until you do, your balances for those accounts will not be reflected.', { replace: { claimCount } })} {t('Claim tokens...')}

+
+ ); +} + +export default React.memo(BannerExtension); diff --git a/packages/page-competitive-list/src/Accounts/BannerExtension.tsx b/packages/page-competitive-list/src/Accounts/BannerExtension.tsx new file mode 100644 index 000000000000..8cff3250bb0f --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/BannerExtension.tsx @@ -0,0 +1,83 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { detect } from 'detect-browser'; +import React from 'react'; +import { Trans } from 'react-i18next'; +import useExtensionCounter from '@polkadot/app-settings/useCounter'; +import { availableExtensions } from '@polkadot/apps-config/extensions'; +import { isWeb3Injected } from '@polkadot/extension-dapp'; +import { stringUpperFirst } from '@polkadot/util'; +import { onlyOnWeb } from '@polkadot/react-api/hoc'; +import { useApi } from '@polkadot/react-hooks'; + +import { useTranslation } from '../translate'; +import Banner from './Banner'; + +// it would have been really good to import this from detect, however... not exported +type Browser = 'chrome' | 'firefox'; + +const browserInfo = detect(); +const browserName: Browser | null = (browserInfo && (browserInfo.name as Browser)) || null; +const isSupported = browserName && Object.keys(availableExtensions).includes(browserName); + +function BannerExtension (): React.ReactElement | null { + const { t } = useTranslation(); + const { hasInjectedAccounts } = useApi(); + const upgradableCount = useExtensionCounter(); + + if (!isSupported || !browserName) { + return null; + } + + if (isWeb3Injected) { + if (hasInjectedAccounts) { + if (!upgradableCount) { + return null; + } + + return ( + +

{t('You have {{upgradableCount}} extensions that need to be updated with the latest chain properties in order to display the correct information for the chain you are connected to. This update includes chain metadata and chain properties.', { replace: { upgradableCount } })}

+

Visit your settings page to apply the updates to the injected extensions.

+
+ ); + } + + return ( + +

{t('One of more extensions has been detected in your browser, however no accounts has been injected.')}

+

{t('Ensure that the extension has accounts, some accounts are visible globally and available for this chain and that you gave the application permission to access accounts from the extension to use them.')}

+
+ ); + } + + return ( + +

{t('It is recommended that you create/store your accounts securely and externally from the app. On {{yourBrowser}} the following browser extensions are available for use -', { + replace: { + yourBrowser: stringUpperFirst(browserName) + } + })}

+
    {availableExtensions[browserName].map(({ desc, link, name }): React.ReactNode => ( +
  • + + {name} + ({t(desc)}) +
  • + )) + }
+

{t('Accounts injected from any of these extensions will appear in this application and be available for use. The above list is updated as more extensions with external signing capability become available.')} {t('Learn more...')}

+
+ ); +} + +export default onlyOnWeb(React.memo(BannerExtension)); diff --git a/packages/page-competitive-list/src/Accounts/index.tsx b/packages/page-competitive-list/src/Accounts/index.tsx new file mode 100644 index 000000000000..3fc973e51e78 --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/index.tsx @@ -0,0 +1,202 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { AccountId, ProxyDefinition, ProxyType, Voting } from '@polkadot/types/interfaces'; +import { Delegation, SortedAccount } from '../types'; + +import BN from 'bn.js'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import styled from 'styled-components'; +import { isLedger } from '@polkadot/react-api'; +import { useApi, useAccounts, useCall, useFavorites, useIpfs, useLoadingDelay, useToggle } from '@polkadot/react-hooks'; +import { FormatBalance } from '@polkadot/react-query'; +import { Button, Input, Table } from '@polkadot/react-components'; +import { BN_ZERO } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import CreateModal from '../modals/Create'; +import ImportModal from '../modals/Import'; +import Ledger from '../modals/Ledger'; +import Multisig from '../modals/MultisigCreate'; +import Proxy from '../modals/ProxiedAdd'; +import Qr from '../modals/Qr'; +import Account from './Account'; +import BannerClaims from './BannerClaims'; +import BannerExtension from './BannerExtension'; +import { sortAccounts } from '../util'; + +interface Balances { + accounts: Record; + balanceTotal?: BN; +} + +interface Sorted { + sortedAccounts: SortedAccount[]; + sortedAddresses: string[]; +} + +interface Props { + className?: string; + onStatusChange: (status: ActionStatus) => void; +} + +const STORE_FAVS = 'accounts:favorites'; + +function Overview ({ className = '', onStatusChange }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api } = useApi(); + const { allAccounts, hasAccounts } = useAccounts(); + const { isIpfs } = useIpfs(); + const [isCreateOpen, toggleCreate] = useToggle(); + const [isImportOpen, toggleImport] = useToggle(); + const [isLedgerOpen, toggleLedger] = useToggle(); + const [isMultisigOpen, toggleMultisig] = useToggle(); + const [isProxyOpen, toggleProxy] = useToggle(); + const [isQrOpen, toggleQr] = useToggle(); + const [favorites, toggleFavorite] = useFavorites(STORE_FAVS); + const [{ balanceTotal }, setBalances] = useState({ accounts: {} }); + const [filterOn, setFilter] = useState(''); + const [sortedAccountsWithDelegation, setSortedAccountsWithDelegation] = useState(); + const [{ sortedAccounts, sortedAddresses }, setSorted] = useState({ sortedAccounts: [], sortedAddresses: [] }); + const delegations = useCall(api.query.democracy?.votingOf?.multi, [sortedAddresses]); + const proxies = useCall<[ProxyDefinition[], BN][]>(api.query.proxy?.proxies.multi, [sortedAddresses], { + transform: (result: [([AccountId, ProxyType] | ProxyDefinition)[], BN][]): [ProxyDefinition[], BN][] => + api.tx.proxy.addProxy.meta.args.length === 3 + ? result as [ProxyDefinition[], BN][] + : (result as [[AccountId, ProxyType][], BN][]).map(([arr, bn]): [ProxyDefinition[], BN] => + [arr.map(([delegate, proxyType]): ProxyDefinition => api.createType('ProxyDefinition', { delegate, proxyType })), bn] + ) + }); + const isLoading = useLoadingDelay(); + + const headerRef = useRef([ + [t('accounts'), 'start', 3], + [t('type')], + [t('tags'), 'start'], + [t('transactions'), 'media--1500'], + [t('Experience goods')], + [t('Confiscated')], + [t('Confiscation (kp)'), 'expand'], + [t('Knowledge power (kp)'), 'expand'], + [], + ]); + + useEffect((): void => { + + const sortedAccounts = sortAccounts(allAccounts, favorites); + const sortedAddresses = sortedAccounts.map((a) => a.account.address); + console.log("sortedAccounts:"+JSON.stringify(sortedAccounts)); + console.log("sortedAccountsWithDelegation:"+JSON.stringify(sortedAccountsWithDelegation)); + + setSorted({ sortedAccounts, sortedAddresses }); + }, [allAccounts, favorites]); + + useEffect(() => { + console.log("delegations:"+delegations) + if (api.query.democracy?.votingOf && !delegations?.length) { + return; + } + + setSortedAccountsWithDelegation( + sortedAccounts?.map((account, index) => { + let delegation: Delegation | undefined; + console.log("delegations2:"+delegations) + if (delegations && delegations[index]?.isDelegating) { + const { balance: amount, conviction, target } = delegations[index].asDelegating; + + delegation = { + accountDelegated: target.toString(), + amount, + conviction + }; + } + console.log("sortedAccountsWithDelegation2:"+JSON.stringify(sortedAccountsWithDelegation)); + return ({ + ...account, + delegation + }); + }) + ); + }, [api, delegations, sortedAccounts]); + + const _setBalance = useCallback( + (account: string, balance: BN) => + setBalances(({ accounts }: Balances): Balances => { + accounts[account] = balance; + console.log("balance:"+balance) + console.log(" accounts[account]:"+ accounts[account]) + return { + accounts, + balanceTotal: Object.values(accounts).reduce((total: BN, value: BN) => total.add(value), BN_ZERO) + }; + }), + [] + ); + + const footer = useMemo(() => ( + + + + + + + + + + + + + + + + ), [balanceTotal]); + + const filter = useMemo(() => ( +
+ ('filter by name or tags')} + onChange={setFilter} + value={filterOn} + /> +
+ ), [filterOn, t]); + + return ( +
+ ("You don't have any accounts. Some features are currently hidden and will only become available once you have accounts.")} + filter={filter} + footer={footer} + header={headerRef.current} + > + {!isLoading && sortedAccountsWithDelegation?.map(({ account, delegation, isFavorite }, index): React.ReactNode => ( + + ))} +
+
+ ); +} + +export default React.memo(styled(Overview)` + .filter--tags { + .ui--Dropdown { + padding-left: 0; + + label { + left: 1.55rem; + } + } + } +`); diff --git a/packages/page-competitive-list/src/Accounts/types.ts b/packages/page-competitive-list/src/Accounts/types.ts new file mode 100644 index 000000000000..b5d7c0b2aea1 --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/types.ts @@ -0,0 +1,31 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { KeyringAddress } from '@polkadot/ui-keyring/types'; + +import { WithTranslation } from 'react-i18next'; + +export { AppProps as ComponentProps } from '@polkadot/react-components/types'; + +export interface BareProps { + className?: string; +} + +export interface I18nProps extends BareProps, WithTranslation {} + +export interface ModalProps { + onClose: () => void; + onStatusChange: (status: ActionStatus) => void; +} + +export interface SortedAccount { + account: KeyringAddress; + children: SortedAccount[]; + isFavorite: boolean; +} + +export interface AmountValidateState { + error: string | null; + warning: string | null; +} diff --git a/packages/page-competitive-list/src/Accounts/useKnownAddresses.ts b/packages/page-competitive-list/src/Accounts/useKnownAddresses.ts new file mode 100644 index 000000000000..3a6d5210b0b0 --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/useKnownAddresses.ts @@ -0,0 +1,15 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { useMemo } from 'react'; +import { useAccounts, useAddresses } from '@polkadot/react-hooks'; + +export default function useKnownAddresses (exclude?: string): string[] { + const { allAccounts } = useAccounts(); + const { allAddresses } = useAddresses(); + + return useMemo( + () => [...allAccounts, ...allAddresses].filter((a) => a !== exclude), + [allAccounts, allAddresses, exclude] + ); +} diff --git a/packages/page-competitive-list/src/Accounts/useMultisigApprovals.ts b/packages/page-competitive-list/src/Accounts/useMultisigApprovals.ts new file mode 100644 index 000000000000..86ec28411ebe --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/useMultisigApprovals.ts @@ -0,0 +1,50 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { H256, Multisig } from '@polkadot/types/interfaces'; + +import { useContext, useEffect, useState } from 'react'; +import { useApi, useIncrement, useIsMountedRef } from '@polkadot/react-hooks'; +import { EventsContext } from '@polkadot/react-query'; +import { Option, StorageKey } from '@polkadot/types'; + +export default function useMultisigApprovals (address: string): [H256, Multisig][] { + const events = useContext(EventsContext); + const { api } = useApi(); + const [multiInfos, setMultiInfos] = useState<[H256, Multisig][]>([]); + const [trigger, incTrigger] = useIncrement(); + const mountedRef = useIsMountedRef(); + + // increment the trigger by looking at all events + // - filter the by multisig module (old utility is not supported) + // - find anything data item where the type is AccountId + // - increment the trigger when at least one matches our address + useEffect((): void => { + events + .filter(({ record: { event: { section } } }) => section === 'multisig') + .reduce((hasMultisig: boolean, { record: { event: { data } } }) => + data.reduce((hasMultisig: boolean, item) => + hasMultisig || (item.toRawType() === 'AccountId' && item.eq(address)), + hasMultisig), + false) && incTrigger(); + }, [address, events, incTrigger]); + + // query all the entries for the multisig, extracting approvals with their hash + useEffect((): void => { + const multiModule = api.query.multisig || api.query.utility; + + multiModule && multiModule.multisigs && + multiModule.multisigs + .entries(address) + .then((infos: [StorageKey, Option][]): void => { + mountedRef.current && setMultiInfos( + infos + .filter(([, opt]) => opt.isSome) + .map(([key, opt]) => [key.args[1] as H256, opt.unwrap()]) + ); + }) + .catch(console.error); + }, [address, api, mountedRef, trigger]); + + return multiInfos; +} diff --git a/packages/page-competitive-list/src/Accounts/useProxies.ts b/packages/page-competitive-list/src/Accounts/useProxies.ts new file mode 100644 index 000000000000..ae2ce60e6023 --- /dev/null +++ b/packages/page-competitive-list/src/Accounts/useProxies.ts @@ -0,0 +1,75 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { AccountId, BalanceOf, ProxyDefinition, ProxyType } from '@polkadot/types/interfaces'; +import { ITuple } from '@polkadot/types/types'; + +import BN from 'bn.js'; +import { useEffect, useState } from 'react'; +import { useAccounts, useApi, useIsMountedRef } from '@polkadot/react-hooks'; +import { Vec } from '@polkadot/types'; +import { BN_ZERO } from '@polkadot/util'; + +interface Proxy { + address: string; + delay: BN; + isOwned: boolean; + type: ProxyType; +} + +interface State { + hasOwned: boolean; + owned: Proxy[]; + proxies: Proxy[]; +} + +const EMPTY_STATE: State = { + hasOwned: false, + owned: [], + proxies: [] +}; + +function createProxy (allAccounts: string[], delegate: AccountId, type: ProxyType, delay = BN_ZERO): Proxy { + const address = delegate.toString(); + + return { + address, + delay, + isOwned: allAccounts.includes(address), + type + }; +} + +export default function useProxies (address?: string | null): State { + const { api } = useApi(); + const { allAccounts } = useAccounts(); + const mountedRef = useIsMountedRef(); + const [known, setState] = useState(EMPTY_STATE); + + useEffect((): void => { + setState(EMPTY_STATE); + + address && api.query.proxy && + api.query.proxy + .proxies | ProxyDefinition>, BalanceOf]>>(address) + .then(([_proxies]): void => { + const proxies = api.tx.proxy.addProxy.meta.args.length === 3 + ? (_proxies as ProxyDefinition[]).map(({ delay, delegate, proxyType }) => + createProxy(allAccounts, delegate, proxyType, delay) + ) + : (_proxies as [AccountId, ProxyType][]).map(([delegate, proxyType]) => + createProxy(allAccounts, delegate, proxyType) + ); + const owned = proxies.filter(({ isOwned }) => isOwned); + + mountedRef.current && setState({ + hasOwned: owned.length !== 0, + owned, + proxies + }); + }) + .catch(console.error); + }, [allAccounts, api, address, mountedRef]); + + return known; +} diff --git a/packages/page-competitive-list/src/CreateAccount.slow.spec.tsx b/packages/page-competitive-list/src/CreateAccount.slow.spec.tsx new file mode 100644 index 000000000000..de66be27ab96 --- /dev/null +++ b/packages/page-competitive-list/src/CreateAccount.slow.spec.tsx @@ -0,0 +1,105 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import AccountsApp from '@polkadot/app-accounts'; +import { MemoryStore } from '@polkadot/app-accounts/test-support/MemoryStore'; +import { lightTheme } from '@polkadot/apps/themes'; +import { Api } from '@polkadot/react-api'; +import '@polkadot/react-components/i18n'; +import { useApi } from '@polkadot/react-hooks'; +import { fireEvent, render, waitForElementToBeRemoved } from '@testing-library/react'; +import React, { PropsWithChildren } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { ThemeProvider } from 'styled-components'; + +const SUBSTRATE_PORT = Number.parseInt(process.env.TEST_SUBSTRATE_PORT || '30333'); + +const WaitForApi = ({ children }: { children: React.ReactNode }): PropsWithChildren | null => { + const api = useApi(); + + return api.isApiReady ? (children) : null; +}; + +const renderAccounts = () => { + const memoryStore = new MemoryStore(); + + return render( + + + + +
+ { /* */ + }}/> +
+
+
+
+
+ ); +}; + +describe.only('--SLOW--: Account Create', () => { + it('new create modal', async () => { + const { findByTestId, findByText, queryByText } = renderAccounts(); + + const addAccountButton = await findByText('Add account', {}, { timeout: 5000 }); + + fireEvent.click(addAccountButton); + + const isSeedSavedCheckbox = await findByText('I have saved my mnemonic seed safely'); + const hiddenCheckbox = isSeedSavedCheckbox as HTMLInputElement; + + fireEvent.click(hiddenCheckbox); + + const nextStepButton = await findByText('Next', {}, { timeout: 4000 }); + + fireEvent.click(nextStepButton); + + const accountNameInput = await findByTestId('name'); + + fireEvent.change(accountNameInput, { target: { value: 'my new account' } }); + + const passwordInput = await findByTestId('password'); + + fireEvent.change(passwordInput, { target: { value: 'password' } }); + + const passwordInput2 = await findByTestId('password (repeat)'); + + fireEvent.change(passwordInput2, { target: { value: 'password' } }); + + const toStep3Button = await findByText('Next', {}, { timeout: 4000 }); + + fireEvent.click(toStep3Button); + + const createAnAccountButton = await findByText('Save', {}, { timeout: 4000 }); + + fireEvent.click(createAnAccountButton); + + await waitForElementToBeRemoved(() => queryByText('Add an account via seed 3/3')); + + expect(await findByText('MY NEW ACCOUNT')).toBeTruthy(); + }); + + it('error message for derivation path', async () => { + const { findByTestId, findByText } = renderAccounts(); + + const addAccountButton = await findByText('Add account', {}, { timeout: 5000 }); + + fireEvent.click(addAccountButton); + + const showAdvancedOptionsButton = await findByText('Advanced creation options', {}, { timeout: 5000 }); + + fireEvent.click(showAdvancedOptionsButton); + + const derivationPathInput = await findByTestId('secret derivation path', {}, { timeout: 5000 }); + + fireEvent.change(derivationPathInput, { target: { value: '//abc//' } }); + + const errorMsg = await findByText('Unable to match provided value to a secret URI', {}, { timeout: 5000 }); + + expect(errorMsg).toBeTruthy(); + }); +}); diff --git a/packages/page-competitive-list/src/GoodsModelList/Account.tsx b/packages/page-competitive-list/src/GoodsModelList/Account.tsx new file mode 100644 index 000000000000..3008618ddee2 --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/Account.tsx @@ -0,0 +1,366 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { SubmittableExtrinsic } from '@polkadot/api/types'; +import { DeriveBalancesAll, DeriveDemocracyLock } from '@polkadot/api-derive/types'; +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { ThemeDef } from '@polkadot/react-components/types'; +import { ProxyDefinition, RecoveryConfig } from '@polkadot/types/interfaces'; +import { KeyringAddress } from '@polkadot/ui-keyring/types'; +import { Delegation } from '../types'; + +import BN from 'bn.js'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import styled, { ThemeContext } from 'styled-components'; +import { ApiPromise } from '@polkadot/api'; +import { getLedger } from '@polkadot/react-api'; +import { AddressInfo, AddressMini, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, LinkExternal, Menu, Popup, StatusContext, Tags } from '@polkadot/react-components'; +import { useAccountInfo, useApi, useCall, useToggle } from '@polkadot/react-hooks'; +import { Option } from '@polkadot/types'; +import keyring from '@polkadot/ui-keyring'; +import { BN_ZERO, formatBalance, formatNumber } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import { createMenuGroup } from '../util'; +import Backup from '../modals/Backup'; +import ChangePass from '../modals/ChangePass'; +import DelegateModal from '../modals/Delegate'; +import Derive from '../modals/Derive'; +import IdentityMain from '../modals/IdentityMain'; +import IdentitySub from '../modals/IdentitySub'; +import ProxyOverview from '../modals/ProxyOverview'; +import MultisigApprove from '../modals/MultisigApprove'; +import RecoverAccount from '../modals/RecoverAccount'; +import RecoverSetup from '../modals/RecoverSetup'; +import Transfer from '../modals/Transfer'; +import UndelegateModal from '../modals/Undelegate'; +import useMultisigApprovals from './useMultisigApprovals'; +import useProxies from './useProxies'; + +interface Props { + account: KeyringAddress; + className?: string; + delegation?: Delegation; + filter: string; + isFavorite: boolean; + proxy?: [ProxyDefinition[], BN]; + setBalance: (address: string, value: BN) => void; + toggleFavorite: (address: string) => void; +} + +interface DemocracyUnlockable { + democracyUnlockTx: SubmittableExtrinsic<'promise'> | null; + ids: BN[]; +} + +function calcVisible (filter: string, name: string, tags: string[]): boolean { + if (filter.length === 0) { + return true; + } + + const _filter = filter.toLowerCase(); + + return tags.reduce((result: boolean, tag: string): boolean => { + return result || tag.toLowerCase().includes(_filter); + }, name.toLowerCase().includes(_filter)); +} + +function createClearDemocracyTx (api: ApiPromise, address: string, unlockableIds: BN[]): SubmittableExtrinsic<'promise'> { + return api.tx.utility.batch( + unlockableIds + .map((id) => api.tx.democracy.removeVote(id)) + .concat(api.tx.democracy.unlock(address)) + ); +} + +const transformRecovery = { + transform: (opt: Option) => opt.unwrapOr(null) +}; + +function Account ({ account: { address, meta }, className = '', delegation, filter, isFavorite, proxy, setBalance, toggleFavorite }: Props): React.ReactElement | null { + console.log("Account--account:"+address) + const { t } = useTranslation(); + const { theme } = useContext(ThemeContext); + const { queueExtrinsic } = useContext(StatusContext); + const api = useApi(); + const bestNumber = useCall(api.api.derive.chain.bestNumber); + console.log("Account--bestNumber:"+JSON.stringify(useCall(api.api.derive.chain.bestNumber))) + console.log("Account--balances.all:"+JSON.stringify(useCall(api.api.derive.balances.all, [address]))) + const balancesAll = useCall(api.api.derive.balances.all, [address]); + const democracyLocks = useCall(api.api.derive.democracy?.locks, [address]); + const recoveryInfo = useCall(api.api.query.recovery?.recoverable, [address], transformRecovery); + const multiInfos = useMultisigApprovals(address); + const proxyInfo = useProxies(address); + const { flags: { isDevelopment, isExternal, isHardware, isInjected, isMultisig, isProxied }, genesisHash, identity, name: accName, onSetGenesisHash, tags } = useAccountInfo(address); + const [{ democracyUnlockTx }, setUnlockableIds] = useState({ democracyUnlockTx: null, ids: [] }); + const [vestingVestTx, setVestingTx] = useState | null>(null); + const [isBackupOpen, toggleBackup] = useToggle(); + const [isDeriveOpen, toggleDerive] = useToggle(); + const [isForgetOpen, toggleForget] = useToggle(); + const [isIdentityMainOpen, toggleIdentityMain] = useToggle(); + const [isIdentitySubOpen, toggleIdentitySub] = useToggle(); + const [isMultisigOpen, toggleMultisig] = useToggle(); + const [isProxyOverviewOpen, toggleProxyOverview] = useToggle(); + const [isPasswordOpen, togglePassword] = useToggle(); + const [isRecoverAccountOpen, toggleRecoverAccount] = useToggle(); + const [isRecoverSetupOpen, toggleRecoverSetup] = useToggle(); + const [isSettingsOpen, toggleSettings] = useToggle(); + const [isTransferOpen, toggleTransfer] = useToggle(); + const [isDelegateOpen, toggleDelegate] = useToggle(); + const [isUndelegateOpen, toggleUndelegate] = useToggle(); + + useEffect((): void => { + if (balancesAll) { + setBalance(address, balancesAll.freeBalance.add(balancesAll.reservedBalance)); + + api.api.tx.vesting?.vest && setVestingTx(() => + balancesAll.vestingLocked.isZero() + ? null + : api.api.tx.vesting.vest() + ); + } + }, [address, api, balancesAll, setBalance]); + + useEffect((): void => { + bestNumber && democracyLocks && setUnlockableIds( + (prev): DemocracyUnlockable => { + const ids = democracyLocks + .filter(({ isFinished, unlockAt }) => isFinished && bestNumber.gt(unlockAt)) + .map(({ referendumId }) => referendumId); + + if (JSON.stringify(prev.ids) === JSON.stringify(ids)) { + return prev; + } + + return { + democracyUnlockTx: createClearDemocracyTx(api.api, address, ids), + ids + }; + } + ); + }, [address, api, bestNumber, democracyLocks]); + + const isVisible = useMemo( + () => calcVisible(filter, accName, tags), + [accName, filter, tags] + ); + + const _onFavorite = useCallback( + () => toggleFavorite(address), + [address, toggleFavorite] + ); + + const _onForget = useCallback( + (): void => { + if (!address) { + return; + } + + const status: Partial = { + account: address, + action: 'forget' + }; + + try { + keyring.forgetAccount(address); + status.status = 'success'; + status.message = t('account forgotten'); + } catch (error) { + status.status = 'error'; + status.message = (error as Error).message; + } + }, + [address, t] + ); + + const _clearDemocracyLocks = useCallback( + () => democracyUnlockTx && queueExtrinsic({ + accountId: address, + extrinsic: democracyUnlockTx + }), + [address, democracyUnlockTx, queueExtrinsic] + ); + + const _vestingVest = useCallback( + () => vestingVestTx && queueExtrinsic({ + accountId: address, + extrinsic: vestingVestTx + }), + [address, queueExtrinsic, vestingVestTx] + ); + + const _showOnHardware = useCallback( + // TODO: we should check the hardwareType from metadata here as well, + // for now we are always assuming hardwareType === 'ledger' + (): void => { + getLedger() + .getAddress(true, meta.accountOffset as number || 0, meta.addressOffset as number || 0) + .catch((error): void => { + console.error(`ledger: ${(error as Error).message}`); + }); + }, + [meta] + ); + + if (!isVisible) { + return null; + } + const goodsId='000033'; + const appId='00010002'; + const goodsTypeId='1000033'; + const testValue1='20201123'; + const testValue2='1'; + const testValue3='正常'; + const KP='99.77 KP'; + return ( + + + + + + {goodsId} + + + {appId} + + + {goodsTypeId} + + + + {isBackupOpen && ( + + )} + {isDelegateOpen && ( + + )} + {isDeriveOpen && ( + + )} + {isForgetOpen && ( + + )} + {isIdentityMainOpen && ( + + )} + {isIdentitySubOpen && ( + + )} + {isPasswordOpen && ( + + )} + {isTransferOpen && ( + + )} + {isProxyOverviewOpen && ( + + )} + {isMultisigOpen && multiInfos && ( + + )} + {isRecoverAccountOpen && ( + + )} + {isRecoverSetupOpen && ( + + )} + {isUndelegateOpen && ( + + )} + + + {testValue1} + + + {testValue2} + + + {testValue3} + + + + + + {KP} + + + + + ); +} + +export default React.memo(styled(Account)` + .tags { + width: 100%; + min-height: 1.5rem; + } +`); diff --git a/packages/page-competitive-list/src/GoodsModelList/Banner.tsx b/packages/page-competitive-list/src/GoodsModelList/Banner.tsx new file mode 100644 index 000000000000..5e5216f16a58 --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/Banner.tsx @@ -0,0 +1,27 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import styled from 'styled-components'; + +interface Props { + children: React.ReactNode; + className?: string; + type: 'warning' | 'error'; +} + +function Banner ({ children, className = '', type }: Props): React.ReactElement | null { + return ( +
+
+ {children} +
+
+ ); +} + +export default React.memo(styled(Banner)` + .box { + padding: 0 0.5rem; + } +`); diff --git a/packages/page-competitive-list/src/GoodsModelList/BannerClaims.tsx b/packages/page-competitive-list/src/GoodsModelList/BannerClaims.tsx new file mode 100644 index 000000000000..25fc546e5002 --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/BannerClaims.tsx @@ -0,0 +1,25 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import useClaimCounter from '@polkadot/app-claims/useCounter'; // exceptionally CRAP idea + +import { useTranslation } from '../translate'; +import Banner from './Banner'; + +function BannerExtension (): React.ReactElement | null { + const claimCount = useClaimCounter(); + const { t } = useTranslation(); + + if (!claimCount) { + return null; + } + + return ( + +

{t('You have {{claimCount}} accounts that need attestations. Use the Claim Tokens app on the navigation bar to complete the process. Until you do, your balances for those accounts will not be reflected.', { replace: { claimCount } })} {t('Claim tokens...')}

+
+ ); +} + +export default React.memo(BannerExtension); diff --git a/packages/page-competitive-list/src/GoodsModelList/BannerExtension.tsx b/packages/page-competitive-list/src/GoodsModelList/BannerExtension.tsx new file mode 100644 index 000000000000..8cff3250bb0f --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/BannerExtension.tsx @@ -0,0 +1,83 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { detect } from 'detect-browser'; +import React from 'react'; +import { Trans } from 'react-i18next'; +import useExtensionCounter from '@polkadot/app-settings/useCounter'; +import { availableExtensions } from '@polkadot/apps-config/extensions'; +import { isWeb3Injected } from '@polkadot/extension-dapp'; +import { stringUpperFirst } from '@polkadot/util'; +import { onlyOnWeb } from '@polkadot/react-api/hoc'; +import { useApi } from '@polkadot/react-hooks'; + +import { useTranslation } from '../translate'; +import Banner from './Banner'; + +// it would have been really good to import this from detect, however... not exported +type Browser = 'chrome' | 'firefox'; + +const browserInfo = detect(); +const browserName: Browser | null = (browserInfo && (browserInfo.name as Browser)) || null; +const isSupported = browserName && Object.keys(availableExtensions).includes(browserName); + +function BannerExtension (): React.ReactElement | null { + const { t } = useTranslation(); + const { hasInjectedAccounts } = useApi(); + const upgradableCount = useExtensionCounter(); + + if (!isSupported || !browserName) { + return null; + } + + if (isWeb3Injected) { + if (hasInjectedAccounts) { + if (!upgradableCount) { + return null; + } + + return ( + +

{t('You have {{upgradableCount}} extensions that need to be updated with the latest chain properties in order to display the correct information for the chain you are connected to. This update includes chain metadata and chain properties.', { replace: { upgradableCount } })}

+

Visit your settings page to apply the updates to the injected extensions.

+
+ ); + } + + return ( + +

{t('One of more extensions has been detected in your browser, however no accounts has been injected.')}

+

{t('Ensure that the extension has accounts, some accounts are visible globally and available for this chain and that you gave the application permission to access accounts from the extension to use them.')}

+
+ ); + } + + return ( + +

{t('It is recommended that you create/store your accounts securely and externally from the app. On {{yourBrowser}} the following browser extensions are available for use -', { + replace: { + yourBrowser: stringUpperFirst(browserName) + } + })}

+
    {availableExtensions[browserName].map(({ desc, link, name }): React.ReactNode => ( +
  • + + {name} + ({t(desc)}) +
  • + )) + }
+

{t('Accounts injected from any of these extensions will appear in this application and be available for use. The above list is updated as more extensions with external signing capability become available.')} {t('Learn more...')}

+
+ ); +} + +export default onlyOnWeb(React.memo(BannerExtension)); diff --git a/packages/page-competitive-list/src/GoodsModelList/index.tsx b/packages/page-competitive-list/src/GoodsModelList/index.tsx new file mode 100644 index 000000000000..5474f283ddb8 --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/index.tsx @@ -0,0 +1,204 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { AccountId, ProxyDefinition, ProxyType, Voting } from '@polkadot/types/interfaces'; +import { Delegation, SortedAccount } from '../types'; + +import BN from 'bn.js'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import styled from 'styled-components'; +import { isLedger } from '@polkadot/react-api'; +import { useApi, useAccounts, useCall, useFavorites, useIpfs, useLoadingDelay, useToggle } from '@polkadot/react-hooks'; +import { FormatBalance } from '@polkadot/react-query'; +import { Button, Input, Table } from '@polkadot/react-components'; +import { BN_ZERO } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import CreateModal from '../modals/Create'; +import ImportModal from '../modals/Import'; +import Ledger from '../modals/Ledger'; +import Multisig from '../modals/MultisigCreate'; +import Proxy from '../modals/ProxiedAdd'; +import Qr from '../modals/Qr'; +import Account from './Account'; +import BannerClaims from './BannerClaims'; +import BannerExtension from './BannerExtension'; +import { sortAccounts } from '../util'; + +interface Balances { + accounts: Record; + balanceTotal?: BN; +} + +interface Sorted { + sortedAccounts: SortedAccount[]; + sortedAddresses: string[]; +} + +interface Props { + className?: string; + onStatusChange: (status: ActionStatus) => void; +} + +const STORE_FAVS = 'accounts:favorites'; + +function Overview ({ className = '', onStatusChange }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api } = useApi(); + const { allAccounts, hasAccounts } = useAccounts(); + const { isIpfs } = useIpfs(); + const [isCreateOpen, toggleCreate] = useToggle(); + const [isImportOpen, toggleImport] = useToggle(); + const [isLedgerOpen, toggleLedger] = useToggle(); + const [isMultisigOpen, toggleMultisig] = useToggle(); + const [isProxyOpen, toggleProxy] = useToggle(); + const [isQrOpen, toggleQr] = useToggle(); + const [favorites, toggleFavorite] = useFavorites(STORE_FAVS); + const [{ balanceTotal }, setBalances] = useState({ accounts: {} }); + const [filterOn, setFilter] = useState(''); + const [sortedAccountsWithDelegation, setSortedAccountsWithDelegation] = useState(); + const [{ sortedAccounts, sortedAddresses }, setSorted] = useState({ sortedAccounts: [], sortedAddresses: [] }); + const delegations = useCall(api.query.democracy?.votingOf?.multi, [sortedAddresses]); + const proxies = useCall<[ProxyDefinition[], BN][]>(api.query.proxy?.proxies.multi, [sortedAddresses], { + transform: (result: [([AccountId, ProxyType] | ProxyDefinition)[], BN][]): [ProxyDefinition[], BN][] => + api.tx.proxy.addProxy.meta.args.length === 3 + ? result as [ProxyDefinition[], BN][] + : (result as [[AccountId, ProxyType][], BN][]).map(([arr, bn]): [ProxyDefinition[], BN] => + [arr.map(([delegate, proxyType]): ProxyDefinition => api.createType('ProxyDefinition', { delegate, proxyType })), bn] + ) + }); + const isLoading = useLoadingDelay(); + + const headerRef = useRef([ + [t('Experience goods id'), 'start', 2], + [t('AppId'), 'start'], + [t('Goods type id'), 'start'], + [t('accounts'), 'start'], + [t('List period'), 'start'], + [t('Ranking'), 'start'], + [t('state'), 'start'], + [t('Review winner'), 'expand'], + [t('Knowledge power (kp)'), 'expand'], + [], + [], + ]); + + useEffect((): void => { + + const sortedAccounts = sortAccounts(allAccounts, favorites); + const sortedAddresses = sortedAccounts.map((a) => a.account.address); + console.log("sortedAccounts:"+JSON.stringify(sortedAccounts)); + console.log("sortedAccountsWithDelegation:"+JSON.stringify(sortedAccountsWithDelegation)); + + setSorted({ sortedAccounts, sortedAddresses }); + }, [allAccounts, favorites]); + + useEffect(() => { + console.log("delegations:"+delegations) + if (api.query.democracy?.votingOf && !delegations?.length) { + return; + } + + setSortedAccountsWithDelegation( + sortedAccounts?.map((account, index) => { + let delegation: Delegation | undefined; + console.log("delegations2:"+delegations) + if (delegations && delegations[index]?.isDelegating) { + const { balance: amount, conviction, target } = delegations[index].asDelegating; + + delegation = { + accountDelegated: target.toString(), + amount, + conviction + }; + } + console.log("sortedAccountsWithDelegation2:"+JSON.stringify(sortedAccountsWithDelegation)); + return ({ + ...account, + delegation + }); + }) + ); + }, [api, delegations, sortedAccounts]); + + const _setBalance = useCallback( + (account: string, balance: BN) => + setBalances(({ accounts }: Balances): Balances => { + accounts[account] = balance; + console.log("balance:"+balance) + console.log(" accounts[account]:"+ accounts[account]) + return { + accounts, + balanceTotal: Object.values(accounts).reduce((total: BN, value: BN) => total.add(value), BN_ZERO) + }; + }), + [] + ); + + const footer = useMemo(() => ( + + + + + + + + + + + + + + + + ), [balanceTotal]); + + const filter = useMemo(() => ( +
+ ('filter by name or tags')} + onChange={setFilter} + value={filterOn} + /> +
+ ), [filterOn, t]); + + return ( +
+ ("You don't have any accounts. Some features are currently hidden and will only become available once you have accounts.")} + filter={filter} + footer={footer} + header={headerRef.current} + > + {!isLoading && sortedAccountsWithDelegation?.map(({ account, delegation, isFavorite }, index): React.ReactNode => ( + + ))} +
+
+ ); +} + +export default React.memo(styled(Overview)` + .filter--tags { + .ui--Dropdown { + padding-left: 0; + + label { + left: 1.55rem; + } + } + } +`); diff --git a/packages/page-competitive-list/src/GoodsModelList/types.ts b/packages/page-competitive-list/src/GoodsModelList/types.ts new file mode 100644 index 000000000000..b5d7c0b2aea1 --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/types.ts @@ -0,0 +1,31 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { KeyringAddress } from '@polkadot/ui-keyring/types'; + +import { WithTranslation } from 'react-i18next'; + +export { AppProps as ComponentProps } from '@polkadot/react-components/types'; + +export interface BareProps { + className?: string; +} + +export interface I18nProps extends BareProps, WithTranslation {} + +export interface ModalProps { + onClose: () => void; + onStatusChange: (status: ActionStatus) => void; +} + +export interface SortedAccount { + account: KeyringAddress; + children: SortedAccount[]; + isFavorite: boolean; +} + +export interface AmountValidateState { + error: string | null; + warning: string | null; +} diff --git a/packages/page-competitive-list/src/GoodsModelList/useKnownAddresses.ts b/packages/page-competitive-list/src/GoodsModelList/useKnownAddresses.ts new file mode 100644 index 000000000000..3a6d5210b0b0 --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/useKnownAddresses.ts @@ -0,0 +1,15 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { useMemo } from 'react'; +import { useAccounts, useAddresses } from '@polkadot/react-hooks'; + +export default function useKnownAddresses (exclude?: string): string[] { + const { allAccounts } = useAccounts(); + const { allAddresses } = useAddresses(); + + return useMemo( + () => [...allAccounts, ...allAddresses].filter((a) => a !== exclude), + [allAccounts, allAddresses, exclude] + ); +} diff --git a/packages/page-competitive-list/src/GoodsModelList/useMultisigApprovals.ts b/packages/page-competitive-list/src/GoodsModelList/useMultisigApprovals.ts new file mode 100644 index 000000000000..86ec28411ebe --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/useMultisigApprovals.ts @@ -0,0 +1,50 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { H256, Multisig } from '@polkadot/types/interfaces'; + +import { useContext, useEffect, useState } from 'react'; +import { useApi, useIncrement, useIsMountedRef } from '@polkadot/react-hooks'; +import { EventsContext } from '@polkadot/react-query'; +import { Option, StorageKey } from '@polkadot/types'; + +export default function useMultisigApprovals (address: string): [H256, Multisig][] { + const events = useContext(EventsContext); + const { api } = useApi(); + const [multiInfos, setMultiInfos] = useState<[H256, Multisig][]>([]); + const [trigger, incTrigger] = useIncrement(); + const mountedRef = useIsMountedRef(); + + // increment the trigger by looking at all events + // - filter the by multisig module (old utility is not supported) + // - find anything data item where the type is AccountId + // - increment the trigger when at least one matches our address + useEffect((): void => { + events + .filter(({ record: { event: { section } } }) => section === 'multisig') + .reduce((hasMultisig: boolean, { record: { event: { data } } }) => + data.reduce((hasMultisig: boolean, item) => + hasMultisig || (item.toRawType() === 'AccountId' && item.eq(address)), + hasMultisig), + false) && incTrigger(); + }, [address, events, incTrigger]); + + // query all the entries for the multisig, extracting approvals with their hash + useEffect((): void => { + const multiModule = api.query.multisig || api.query.utility; + + multiModule && multiModule.multisigs && + multiModule.multisigs + .entries(address) + .then((infos: [StorageKey, Option][]): void => { + mountedRef.current && setMultiInfos( + infos + .filter(([, opt]) => opt.isSome) + .map(([key, opt]) => [key.args[1] as H256, opt.unwrap()]) + ); + }) + .catch(console.error); + }, [address, api, mountedRef, trigger]); + + return multiInfos; +} diff --git a/packages/page-competitive-list/src/GoodsModelList/useProxies.ts b/packages/page-competitive-list/src/GoodsModelList/useProxies.ts new file mode 100644 index 000000000000..ae2ce60e6023 --- /dev/null +++ b/packages/page-competitive-list/src/GoodsModelList/useProxies.ts @@ -0,0 +1,75 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { AccountId, BalanceOf, ProxyDefinition, ProxyType } from '@polkadot/types/interfaces'; +import { ITuple } from '@polkadot/types/types'; + +import BN from 'bn.js'; +import { useEffect, useState } from 'react'; +import { useAccounts, useApi, useIsMountedRef } from '@polkadot/react-hooks'; +import { Vec } from '@polkadot/types'; +import { BN_ZERO } from '@polkadot/util'; + +interface Proxy { + address: string; + delay: BN; + isOwned: boolean; + type: ProxyType; +} + +interface State { + hasOwned: boolean; + owned: Proxy[]; + proxies: Proxy[]; +} + +const EMPTY_STATE: State = { + hasOwned: false, + owned: [], + proxies: [] +}; + +function createProxy (allAccounts: string[], delegate: AccountId, type: ProxyType, delay = BN_ZERO): Proxy { + const address = delegate.toString(); + + return { + address, + delay, + isOwned: allAccounts.includes(address), + type + }; +} + +export default function useProxies (address?: string | null): State { + const { api } = useApi(); + const { allAccounts } = useAccounts(); + const mountedRef = useIsMountedRef(); + const [known, setState] = useState(EMPTY_STATE); + + useEffect((): void => { + setState(EMPTY_STATE); + + address && api.query.proxy && + api.query.proxy + .proxies | ProxyDefinition>, BalanceOf]>>(address) + .then(([_proxies]): void => { + const proxies = api.tx.proxy.addProxy.meta.args.length === 3 + ? (_proxies as ProxyDefinition[]).map(({ delay, delegate, proxyType }) => + createProxy(allAccounts, delegate, proxyType, delay) + ) + : (_proxies as [AccountId, ProxyType][]).map(([delegate, proxyType]) => + createProxy(allAccounts, delegate, proxyType) + ); + const owned = proxies.filter(({ isOwned }) => isOwned); + + mountedRef.current && setState({ + hasOwned: owned.length !== 0, + owned, + proxies + }); + }) + .catch(console.error); + }, [allAccounts, api, address, mountedRef]); + + return known; +} diff --git a/packages/page-competitive-list/src/Sidebar/Balances.tsx b/packages/page-competitive-list/src/Sidebar/Balances.tsx new file mode 100644 index 000000000000..d011beabffc0 --- /dev/null +++ b/packages/page-competitive-list/src/Sidebar/Balances.tsx @@ -0,0 +1,56 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import styled from 'styled-components'; +import { AddressInfo, Icon } from '@polkadot/react-components'; + +import { useTranslation } from '../translate'; + +interface Props { + address: string; + className?: string; +} + +const WITH_BALANCE = { available: true, bonded: true, free: true, locked: true, reserved: true, total: true }; + +function Balances ({ address, className }: Props): React.ReactElement | null { + const { t } = useTranslation(); + console.log("WITH_BALANCE:"+JSON.stringify(WITH_BALANCE)) + return ( +
+
+
+ +   + {t('balance')} +
+
+ +
+ ); +} + +export default React.memo(styled(Balances)` + .balanceExpander { + .column.column--expander { + width: auto; + + label { + color: inherit; + font-size: 0.93rem; + font-weight: 400; + } + + .ui--Expander-content .ui--FormatBalance-value { + font-size: 0.93rem; + } + } + } +`); diff --git a/packages/page-competitive-list/src/Sidebar/Flags.tsx b/packages/page-competitive-list/src/Sidebar/Flags.tsx new file mode 100644 index 000000000000..6259e583f6e5 --- /dev/null +++ b/packages/page-competitive-list/src/Sidebar/Flags.tsx @@ -0,0 +1,96 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { AddressFlags } from '@polkadot/react-hooks/types'; + +import React from 'react'; +import { Tag } from '@polkadot/react-components'; + +import { useTranslation } from '../translate'; + +interface Props { + flags: AddressFlags; +} + +function Flags ({ flags: { isCouncil, isDevelopment, isExternal, isInjected, isMultisig, isProxied, isSociety, isSudo, isTechCommittee } }: Props): React.ReactElement | null { + const { t } = useTranslation(); + const hasFlags = isCouncil || isDevelopment || isExternal || isInjected || isMultisig || isProxied || isSociety || isSudo || isTechCommittee; + + if (!hasFlags) { + return null; + } + + return ( +
+ {isExternal && ( + isMultisig + ? ( + ('Multisig')} + size='tiny' + /> + ) + : isProxied + ? ( + ('External')} + size='tiny' + /> + ) + : ( + ('External')} + size='tiny' + /> + ) + )} + {isInjected && ( + ('Injected')} + size='tiny' + /> + )} + {isDevelopment && ( + ('Test account')} + size='tiny' + /> + )} + {isCouncil && ( + ('Council')} + size='tiny' + /> + )} + {isSociety && ( + ('Society')} + size='tiny' + /> + )} + {isTechCommittee && ( + ('Technical committee')} + size='tiny' + /> + )} + {isSudo && ( + ('Sudo key')} + size='tiny' + /> + )} +
+ ); +} + +export default React.memo(Flags); diff --git a/packages/page-competitive-list/src/Sidebar/Identity.tsx b/packages/page-competitive-list/src/Sidebar/Identity.tsx new file mode 100644 index 000000000000..55f314e7b88a --- /dev/null +++ b/packages/page-competitive-list/src/Sidebar/Identity.tsx @@ -0,0 +1,211 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { AddressIdentity } from '@polkadot/react-hooks/types'; +import { AccountId, BalanceOf } from '@polkadot/types/interfaces'; + +import React from 'react'; +import { AddressMini, AvatarItem, Expander, Icon, IconLink, Tag } from '@polkadot/react-components'; +import { useApi, useCall, useRegistrars, useToggle } from '@polkadot/react-hooks'; +import { isHex } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import RegistrarJudgement from './RegistrarJudgement'; + +interface Props { + address: string; + identity?: AddressIdentity; +} + +function Identity ({ address, identity }: Props): React.ReactElement | null { + const { t } = useTranslation(); + const { api } = useApi(); + const { isRegistrar, registrars } = useRegistrars(); + const [isJudgementOpen, toggleIsJudgementOpen] = useToggle(); + const subs = useCall<[BalanceOf, AccountId[]]>(api.query.identity?.subsOf, [address])?.[1]; + + if (!identity || !identity.isExistent || !api.query.identity?.identityOf) { + return null; + } + + return ( +
+
+
+
+ +   + {t('identity')} +
+ + {identity.judgements.length}  + { + identity.judgements.length + ? (identity.isGood + ? (identity.isKnownGood ? t('Known good') : t('Reasonable')) + : (identity.isErroneous ? t('Erroneous') : t('Low quality')) + ) + : t('No judgments') + } + + } + size='tiny' + /> +
+
+ + // : + // + + } + subtitle={identity.legal} + title={identity.display} + /> +
+ {identity.parent && ( +
+
{t('parent')}
+
+ +
+
+ )} + {identity.email && ( +
+
{t('email')}
+
+ {isHex(identity.email) + ? identity.email + : ( + + {identity.email} + + )} +
+
+ )} + {identity.web && ( +
+
{t('website')}
+
+ {isHex(identity.web) + ? identity.web + : ( + + {identity.web} + + )} +
+
+ )} + {identity.twitter && ( +
+
{t('twitter')}
+
+ {isHex(identity.twitter) + ? identity.twitter + : ( + + {identity.twitter} + + )} +
+
+ )} + {identity.riot && ( +
+
{t('riot')}
+
+ {identity.riot} +
+
+ )} + {!!subs?.length && ( +
+ {subs.length > 1 + ?
{t('subs')}
+ :
{t('sub')}
+ } +
+ +
+ {subs.map((sub) => + + )} +
+
+
+
)} +
+
+
+ {isRegistrar && ( +
+
+
    +
  • + ('Add identity judgment')} + onClick={toggleIsJudgementOpen} + /> +
  • +
+
+
+ )} + {isJudgementOpen && isRegistrar && ( + + )} +
+ ); +} + +export default React.memo(Identity); diff --git a/packages/page-competitive-list/src/Sidebar/Multisig.tsx b/packages/page-competitive-list/src/Sidebar/Multisig.tsx new file mode 100644 index 000000000000..778e2c7a92db --- /dev/null +++ b/packages/page-competitive-list/src/Sidebar/Multisig.tsx @@ -0,0 +1,55 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { KeyringJson$Meta } from '@polkadot/ui-keyring/types'; + +import React from 'react'; +import { AddressMini, Icon, Static } from '@polkadot/react-components'; + +import { useTranslation } from '../translate'; + +interface Props { + isMultisig: boolean; + meta?: KeyringJson$Meta; +} + +function Multisig ({ isMultisig, meta }: Props): React.ReactElement | null { + const { t } = useTranslation(); + + if (!isMultisig || !meta) { + return null; + } + + const { threshold, who } = meta; + + return ( +
+
+
+ +   + {t('multisig')} +
+
+ ('threshold')} + > + {threshold}/{(who as string[]).length} + + ('signatories')} + > + {(who as string[])?.map((address) => ( + + ))} + +
+ ); +} + +export default React.memo(Multisig); diff --git a/packages/page-competitive-list/src/Sidebar/RegistrarJudgement.tsx b/packages/page-competitive-list/src/Sidebar/RegistrarJudgement.tsx new file mode 100644 index 000000000000..00fd8da94f98 --- /dev/null +++ b/packages/page-competitive-list/src/Sidebar/RegistrarJudgement.tsx @@ -0,0 +1,82 @@ +// Copyright 2017-2020 @polkadot/react-query authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React, { useEffect, useState } from 'react'; +import { Dropdown, Input, InputAddress, Modal, TxButton } from '@polkadot/react-components'; + +import { useTranslation } from '../translate'; + +interface Props { + address: string; + registrars: { address: string; index: number }[]; + toggleJudgement: () => void; +} + +const JUDGEMENT_ENUM = [ + { text: 'Unknown', value: 0 }, + { text: 'Fee paid', value: 1 }, + { text: 'Reasonable', value: 2 }, + { text: 'Known good', value: 3 }, + { text: 'Out of date', value: 4 }, + { text: 'Low quality', value: 5 } +]; + +function RegistrarJudgement ({ address, registrars, toggleJudgement }: Props): React.ReactElement { + const { t } = useTranslation(); + const [addresses] = useState(registrars.map(({ address }) => address)); + const [judgementAccountId, setJudgementAccountId] = useState(null); + const [judgementEnum, setJudgementEnum] = useState(2); // Reasonable + const [registrarIndex, setRegistrarIndex] = useState(-1); + + // find the id of our registrar in the list + useEffect((): void => { + const registrar = registrars.find(({ address }) => judgementAccountId === address); + + setRegistrarIndex( + registrar + ? registrar.index + : -1 + ); + }, [judgementAccountId, registrars]); + + return ( + ('Provide judgement')} + onClose={toggleJudgement} + size='small' + > + + ('registrar account')} + onChange={setJudgementAccountId} + type='account' + /> + ('registrar index')} + value={registrarIndex === -1 ? t('invalid/unknown registrar account') : registrarIndex.toString()} + /> + ('judgement')} + onChange={setJudgementEnum} + options={JUDGEMENT_ENUM} + value={judgementEnum} + /> + + + ('Judge')} + onStart={toggleJudgement} + params={[registrarIndex, address, judgementEnum]} + tx='identity.provideJudgement' + /> + + + ); +} + +export default React.memo(RegistrarJudgement); diff --git a/packages/page-competitive-list/src/Sidebar/Sidebar.tsx b/packages/page-competitive-list/src/Sidebar/Sidebar.tsx new file mode 100644 index 000000000000..6c91d348aad5 --- /dev/null +++ b/packages/page-competitive-list/src/Sidebar/Sidebar.tsx @@ -0,0 +1,304 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { ThemeProps } from '@polkadot/react-components/types'; + +import React, { useCallback } from 'react'; +import styled from 'styled-components'; +import { useAccountInfo, useToggle } from '@polkadot/react-hooks'; +import { colorLink } from '@polkadot/react-components/styles/theme'; +import { AccountName, Button, Icon, IdentityIcon, Input, LinkExternal, Sidebar, Tags } from '@polkadot/react-components'; + +import Transfer from '../modals/Transfer'; +import { useTranslation } from '../translate'; +import Balances from './Balances'; +import Flags from './Flags'; +import Identity from './Identity'; +import Multisig from './Multisig'; + +interface Props { + address: string; + className?: string; + onClose: () => void; + onUpdateName: () => void; +} + +function FullSidebar ({ address, className = '', onClose, onUpdateName }: Props): React.ReactElement { + const { t } = useTranslation(); + const { accountIndex, flags, identity, isEditingName, isEditingTags, meta, name, onForgetAddress, onSaveName, onSaveTags, setName, setTags, tags, toggleIsEditingName, toggleIsEditingTags } = useAccountInfo(address); + const [isTransferOpen, toggleIsTransferOpen] = useToggle(); + + const _onForgetAddress = useCallback( + (): void => { + onForgetAddress(); + onUpdateName && onUpdateName(); + }, + [onForgetAddress, onUpdateName] + ); + + const _onUpdateName = useCallback( + (): void => { + onSaveName(); + onUpdateName && onUpdateName(); + }, + [onSaveName, onUpdateName] + ); + + return ( + +
+ +
+ {address} +
+ {accountIndex && ( +
+ {accountIndex} +
+ )} + + ) + : flags.isEditable + ? (name.toUpperCase() || t('')) + : undefined + } + value={address} + withSidebar={false} + > + {(!isEditingName && flags.isEditable) && ( + + )} + +
+ +
+ +
+ +
+
+ + + +
+ +
+
+ ); +} + +export default React.memo(styled(FullSidebar)(({ theme }: ThemeProps) => ` + input { + width: auto !important; + } + + .ui--AddressMenu-header { + align-items: center; + background: ${theme.bgTabs}; + border-bottom: 1px solid ${theme.borderTable}; + display: flex; + flex-direction: column; + justify-content: center; + margin: -1rem -1rem 1rem -1rem; + padding: 1rem; + } + + .ui--AddressMenu-addr { + font-family: ${theme.fontMono}; + margin: 0.5rem 0; + overflow: hidden; + text-align: center; + text-overflow: ellipsis; + width: 100%; + } + + .ui--AddressMenu-addr+.ui--AddressMenu-addr { + margin-top: -0.25rem; + } + + section { + &:not(:last-child) { + margin-bottom: 1.4rem; + } + + .ui--AddressMenu-sectionHeader { + display: inline-flex; + color: ${theme.color}; + margin-bottom: 0.4rem; + width: 100%; + + & > :first-child { + flex: 1; + } + } + } + + .ui--AddressMenu-identity { + .ui--AddressMenu-identityTable { + font-size: 0.93rem; + margin-top: 0.3rem; + + .tr { + display: inline-flex; + align-items: center; + width: 100%; + + .th { + font-weight: 400; + text-align: right; + flex-basis: 20%; + + &.top { + align-self: flex-start; + } + } + + .td { + flex: 1; + overflow: hidden; + padding-left: 0.6rem; + text-overflow: ellipsis; + } + } + } + + .parent, .subs { + padding: 0 !important; + } + } + + .ui--AddressMenu-tags, + .ui--AddressMenu-flags { + margin-bottom: 0.75rem; + } + + .ui--AddressMenu-flags { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + + > * { + margin-bottom: 0.4rem; + + &:not(:first-child) { + margin-left: 1rem; + margin-right: 0; + } + } + } + + .ui--AddressMenu-identityIcon { + background: ${colorLink}66; + } + + .ui--AddressMenu-actions { + ul { + list-style-type: none; + margin-block-start: 0; + margin-block-end: 0; + padding-inline-start: 1rem; + + li { + margin: 0.2rem 0; + } + } + } + + .tags--toggle { + display: inline-block; + } + + .inline-icon { + cursor: pointer; + margin: 0 0 0 0.5rem; + color: ${colorLink}; + } + + .name--input { + .ui.input { + margin: 0 !important; + + > input { + padding: 0 !important; + background: rgba(230, 230, 230, 0.8) !important; + border: 0 !important; + border-radius: 0 !important; + box-shadow: 0 3px 3px rgba(0,0,0,.2); + } + } + } +`)); diff --git a/packages/page-competitive-list/src/Sidebar/index.tsx b/packages/page-competitive-list/src/Sidebar/index.tsx new file mode 100644 index 000000000000..fc614ee8ab7b --- /dev/null +++ b/packages/page-competitive-list/src/Sidebar/index.tsx @@ -0,0 +1,42 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { StringOrNull, VoidFn } from '@polkadot/react-components/types'; + +import React, { useCallback, useState } from 'react'; + +import Sidebar from './Sidebar'; + +type ToggleContext = undefined | (([address, onUpdateName]: [StringOrNull, VoidFn | null]) => void); + +interface Props { + children: React.ReactNode; +} + +const AccountSidebarToggle: React.Context = React.createContext(undefined); + +function AccountSidebar ({ children }: Props): React.ReactElement { + const [[address, onUpdateName], setAddress] = useState<[StringOrNull, VoidFn | null]>([null, null]); + + const onClose = useCallback( + () => setAddress([null, null]), + [] + ); + + return ( + + {children} + {address && ( + + )} + + ); +} + +export { AccountSidebarToggle }; + +export default React.memo(AccountSidebar); diff --git a/packages/page-competitive-list/src/WholeNetworkList/Account.tsx b/packages/page-competitive-list/src/WholeNetworkList/Account.tsx new file mode 100644 index 000000000000..0e754e3f74c9 --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/Account.tsx @@ -0,0 +1,363 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { SubmittableExtrinsic } from '@polkadot/api/types'; +import { DeriveBalancesAll, DeriveDemocracyLock } from '@polkadot/api-derive/types'; +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { ThemeDef } from '@polkadot/react-components/types'; +import { ProxyDefinition, RecoveryConfig } from '@polkadot/types/interfaces'; +import { KeyringAddress } from '@polkadot/ui-keyring/types'; +import { Delegation } from '../types'; + +import BN from 'bn.js'; +import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import styled, { ThemeContext } from 'styled-components'; +import { ApiPromise } from '@polkadot/api'; +import { getLedger } from '@polkadot/react-api'; +import { AddressInfo, AddressMini, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, LinkExternal, Menu, Popup, StatusContext, Tags } from '@polkadot/react-components'; +import { useAccountInfo, useApi, useCall, useToggle } from '@polkadot/react-hooks'; +import { Option } from '@polkadot/types'; +import keyring from '@polkadot/ui-keyring'; +import { BN_ZERO, formatBalance, formatNumber } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import { createMenuGroup } from '../util'; +import Backup from '../modals/Backup'; +import ChangePass from '../modals/ChangePass'; +import DelegateModal from '../modals/Delegate'; +import Derive from '../modals/Derive'; +import IdentityMain from '../modals/IdentityMain'; +import IdentitySub from '../modals/IdentitySub'; +import ProxyOverview from '../modals/ProxyOverview'; +import MultisigApprove from '../modals/MultisigApprove'; +import RecoverAccount from '../modals/RecoverAccount'; +import RecoverSetup from '../modals/RecoverSetup'; +import Transfer from '../modals/Transfer'; +import UndelegateModal from '../modals/Undelegate'; +import useMultisigApprovals from './useMultisigApprovals'; +import useProxies from './useProxies'; + +interface Props { + account: KeyringAddress; + className?: string; + delegation?: Delegation; + filter: string; + isFavorite: boolean; + proxy?: [ProxyDefinition[], BN]; + setBalance: (address: string, value: BN) => void; + toggleFavorite: (address: string) => void; +} + +interface DemocracyUnlockable { + democracyUnlockTx: SubmittableExtrinsic<'promise'> | null; + ids: BN[]; +} + +function calcVisible (filter: string, name: string, tags: string[]): boolean { + if (filter.length === 0) { + return true; + } + + const _filter = filter.toLowerCase(); + + return tags.reduce((result: boolean, tag: string): boolean => { + return result || tag.toLowerCase().includes(_filter); + }, name.toLowerCase().includes(_filter)); +} + +function createClearDemocracyTx (api: ApiPromise, address: string, unlockableIds: BN[]): SubmittableExtrinsic<'promise'> { + return api.tx.utility.batch( + unlockableIds + .map((id) => api.tx.democracy.removeVote(id)) + .concat(api.tx.democracy.unlock(address)) + ); +} + +const transformRecovery = { + transform: (opt: Option) => opt.unwrapOr(null) +}; + +function Account ({ account: { address, meta }, className = '', delegation, filter, isFavorite, proxy, setBalance, toggleFavorite }: Props): React.ReactElement | null { + console.log("Account--account:"+address) + const { t } = useTranslation(); + const { theme } = useContext(ThemeContext); + const { queueExtrinsic } = useContext(StatusContext); + const api = useApi(); + const bestNumber = useCall(api.api.derive.chain.bestNumber); + console.log("Account--bestNumber:"+JSON.stringify(useCall(api.api.derive.chain.bestNumber))) + console.log("Account--balances.all:"+JSON.stringify(useCall(api.api.derive.balances.all, [address]))) + const balancesAll = useCall(api.api.derive.balances.all, [address]); + const democracyLocks = useCall(api.api.derive.democracy?.locks, [address]); + const recoveryInfo = useCall(api.api.query.recovery?.recoverable, [address], transformRecovery); + const multiInfos = useMultisigApprovals(address); + const proxyInfo = useProxies(address); + const { flags: { isDevelopment, isExternal, isHardware, isInjected, isMultisig, isProxied }, genesisHash, identity, name: accName, onSetGenesisHash, tags } = useAccountInfo(address); + const [{ democracyUnlockTx }, setUnlockableIds] = useState({ democracyUnlockTx: null, ids: [] }); + const [vestingVestTx, setVestingTx] = useState | null>(null); + const [isBackupOpen, toggleBackup] = useToggle(); + const [isDeriveOpen, toggleDerive] = useToggle(); + const [isForgetOpen, toggleForget] = useToggle(); + const [isIdentityMainOpen, toggleIdentityMain] = useToggle(); + const [isIdentitySubOpen, toggleIdentitySub] = useToggle(); + const [isMultisigOpen, toggleMultisig] = useToggle(); + const [isProxyOverviewOpen, toggleProxyOverview] = useToggle(); + const [isPasswordOpen, togglePassword] = useToggle(); + const [isRecoverAccountOpen, toggleRecoverAccount] = useToggle(); + const [isRecoverSetupOpen, toggleRecoverSetup] = useToggle(); + const [isSettingsOpen, toggleSettings] = useToggle(); + const [isTransferOpen, toggleTransfer] = useToggle(); + const [isDelegateOpen, toggleDelegate] = useToggle(); + const [isUndelegateOpen, toggleUndelegate] = useToggle(); + + useEffect((): void => { + if (balancesAll) { + setBalance(address, balancesAll.freeBalance.add(balancesAll.reservedBalance)); + + api.api.tx.vesting?.vest && setVestingTx(() => + balancesAll.vestingLocked.isZero() + ? null + : api.api.tx.vesting.vest() + ); + } + }, [address, api, balancesAll, setBalance]); + + useEffect((): void => { + bestNumber && democracyLocks && setUnlockableIds( + (prev): DemocracyUnlockable => { + const ids = democracyLocks + .filter(({ isFinished, unlockAt }) => isFinished && bestNumber.gt(unlockAt)) + .map(({ referendumId }) => referendumId); + + if (JSON.stringify(prev.ids) === JSON.stringify(ids)) { + return prev; + } + + return { + democracyUnlockTx: createClearDemocracyTx(api.api, address, ids), + ids + }; + } + ); + }, [address, api, bestNumber, democracyLocks]); + + const isVisible = useMemo( + () => calcVisible(filter, accName, tags), + [accName, filter, tags] + ); + + const _onFavorite = useCallback( + () => toggleFavorite(address), + [address, toggleFavorite] + ); + + const _onForget = useCallback( + (): void => { + if (!address) { + return; + } + + const status: Partial = { + account: address, + action: 'forget' + }; + + try { + keyring.forgetAccount(address); + status.status = 'success'; + status.message = t('account forgotten'); + } catch (error) { + status.status = 'error'; + status.message = (error as Error).message; + } + }, + [address, t] + ); + + const _clearDemocracyLocks = useCallback( + () => democracyUnlockTx && queueExtrinsic({ + accountId: address, + extrinsic: democracyUnlockTx + }), + [address, democracyUnlockTx, queueExtrinsic] + ); + + const _vestingVest = useCallback( + () => vestingVestTx && queueExtrinsic({ + accountId: address, + extrinsic: vestingVestTx + }), + [address, queueExtrinsic, vestingVestTx] + ); + + const _showOnHardware = useCallback( + // TODO: we should check the hardwareType from metadata here as well, + // for now we are always assuming hardwareType === 'ledger' + (): void => { + getLedger() + .getAddress(true, meta.accountOffset as number || 0, meta.addressOffset as number || 0) + .catch((error): void => { + console.error(`ledger: ${(error as Error).message}`); + }); + }, + [meta] + ); + + if (!isVisible) { + return null; + } + const goodsId='000033'; + const appId='00010002'; + const testValue1='20201123'; + const testValue2='1'; + const testValue3='正常'; + const KP='99.77 KP'; + return ( + + + + + + {goodsId} + + + {appId} + + + + {isBackupOpen && ( + + )} + {isDelegateOpen && ( + + )} + {isDeriveOpen && ( + + )} + {isForgetOpen && ( + + )} + {isIdentityMainOpen && ( + + )} + {isIdentitySubOpen && ( + + )} + {isPasswordOpen && ( + + )} + {isTransferOpen && ( + + )} + {isProxyOverviewOpen && ( + + )} + {isMultisigOpen && multiInfos && ( + + )} + {isRecoverAccountOpen && ( + + )} + {isRecoverSetupOpen && ( + + )} + {isUndelegateOpen && ( + + )} + + + {testValue1} + + + {testValue2} + + + {testValue3} + + + + + + {KP} + + + + + + ); +} + +export default React.memo(styled(Account)` + .tags { + width: 100%; + min-height: 1.5rem; + } +`); diff --git a/packages/page-competitive-list/src/WholeNetworkList/Banner.tsx b/packages/page-competitive-list/src/WholeNetworkList/Banner.tsx new file mode 100644 index 000000000000..5e5216f16a58 --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/Banner.tsx @@ -0,0 +1,27 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import styled from 'styled-components'; + +interface Props { + children: React.ReactNode; + className?: string; + type: 'warning' | 'error'; +} + +function Banner ({ children, className = '', type }: Props): React.ReactElement | null { + return ( +
+
+ {children} +
+
+ ); +} + +export default React.memo(styled(Banner)` + .box { + padding: 0 0.5rem; + } +`); diff --git a/packages/page-competitive-list/src/WholeNetworkList/BannerClaims.tsx b/packages/page-competitive-list/src/WholeNetworkList/BannerClaims.tsx new file mode 100644 index 000000000000..25fc546e5002 --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/BannerClaims.tsx @@ -0,0 +1,25 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import useClaimCounter from '@polkadot/app-claims/useCounter'; // exceptionally CRAP idea + +import { useTranslation } from '../translate'; +import Banner from './Banner'; + +function BannerExtension (): React.ReactElement | null { + const claimCount = useClaimCounter(); + const { t } = useTranslation(); + + if (!claimCount) { + return null; + } + + return ( + +

{t('You have {{claimCount}} accounts that need attestations. Use the Claim Tokens app on the navigation bar to complete the process. Until you do, your balances for those accounts will not be reflected.', { replace: { claimCount } })} {t('Claim tokens...')}

+
+ ); +} + +export default React.memo(BannerExtension); diff --git a/packages/page-competitive-list/src/WholeNetworkList/BannerExtension.tsx b/packages/page-competitive-list/src/WholeNetworkList/BannerExtension.tsx new file mode 100644 index 000000000000..8cff3250bb0f --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/BannerExtension.tsx @@ -0,0 +1,83 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { detect } from 'detect-browser'; +import React from 'react'; +import { Trans } from 'react-i18next'; +import useExtensionCounter from '@polkadot/app-settings/useCounter'; +import { availableExtensions } from '@polkadot/apps-config/extensions'; +import { isWeb3Injected } from '@polkadot/extension-dapp'; +import { stringUpperFirst } from '@polkadot/util'; +import { onlyOnWeb } from '@polkadot/react-api/hoc'; +import { useApi } from '@polkadot/react-hooks'; + +import { useTranslation } from '../translate'; +import Banner from './Banner'; + +// it would have been really good to import this from detect, however... not exported +type Browser = 'chrome' | 'firefox'; + +const browserInfo = detect(); +const browserName: Browser | null = (browserInfo && (browserInfo.name as Browser)) || null; +const isSupported = browserName && Object.keys(availableExtensions).includes(browserName); + +function BannerExtension (): React.ReactElement | null { + const { t } = useTranslation(); + const { hasInjectedAccounts } = useApi(); + const upgradableCount = useExtensionCounter(); + + if (!isSupported || !browserName) { + return null; + } + + if (isWeb3Injected) { + if (hasInjectedAccounts) { + if (!upgradableCount) { + return null; + } + + return ( + +

{t('You have {{upgradableCount}} extensions that need to be updated with the latest chain properties in order to display the correct information for the chain you are connected to. This update includes chain metadata and chain properties.', { replace: { upgradableCount } })}

+

Visit your settings page to apply the updates to the injected extensions.

+
+ ); + } + + return ( + +

{t('One of more extensions has been detected in your browser, however no accounts has been injected.')}

+

{t('Ensure that the extension has accounts, some accounts are visible globally and available for this chain and that you gave the application permission to access accounts from the extension to use them.')}

+
+ ); + } + + return ( + +

{t('It is recommended that you create/store your accounts securely and externally from the app. On {{yourBrowser}} the following browser extensions are available for use -', { + replace: { + yourBrowser: stringUpperFirst(browserName) + } + })}

+
    {availableExtensions[browserName].map(({ desc, link, name }): React.ReactNode => ( +
  • + + {name} + ({t(desc)}) +
  • + )) + }
+

{t('Accounts injected from any of these extensions will appear in this application and be available for use. The above list is updated as more extensions with external signing capability become available.')} {t('Learn more...')}

+
+ ); +} + +export default onlyOnWeb(React.memo(BannerExtension)); diff --git a/packages/page-competitive-list/src/WholeNetworkList/index.tsx b/packages/page-competitive-list/src/WholeNetworkList/index.tsx new file mode 100644 index 000000000000..ec52c906b9b9 --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/index.tsx @@ -0,0 +1,204 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { AccountId, ProxyDefinition, ProxyType, Voting } from '@polkadot/types/interfaces'; +import { Delegation, SortedAccount } from '../types'; + +import BN from 'bn.js'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import styled from 'styled-components'; +import { isLedger } from '@polkadot/react-api'; +import { useApi, useAccounts, useCall, useFavorites, useIpfs, useLoadingDelay, useToggle } from '@polkadot/react-hooks'; +import { FormatBalance } from '@polkadot/react-query'; +import { Button, Input, Table } from '@polkadot/react-components'; +import { BN_ZERO } from '@polkadot/util'; + +import { useTranslation } from '../translate'; +import CreateModal from '../modals/Create'; +import ImportModal from '../modals/Import'; +import Ledger from '../modals/Ledger'; +import Multisig from '../modals/MultisigCreate'; +import Proxy from '../modals/ProxiedAdd'; +import Qr from '../modals/Qr'; +import Account from './Account'; +import BannerClaims from './BannerClaims'; +import BannerExtension from './BannerExtension'; +import { sortAccounts } from '../util'; + +interface Balances { + accounts: Record; + balanceTotal?: BN; +} + +interface Sorted { + sortedAccounts: SortedAccount[]; + sortedAddresses: string[]; +} + +interface Props { + className?: string; + onStatusChange: (status: ActionStatus) => void; +} + +const STORE_FAVS = 'accounts:favorites'; + +function Overview ({ className = '', onStatusChange }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api } = useApi(); + const { allAccounts, hasAccounts } = useAccounts(); + const { isIpfs } = useIpfs(); + const [isCreateOpen, toggleCreate] = useToggle(); + const [isImportOpen, toggleImport] = useToggle(); + const [isLedgerOpen, toggleLedger] = useToggle(); + const [isMultisigOpen, toggleMultisig] = useToggle(); + const [isProxyOpen, toggleProxy] = useToggle(); + const [isQrOpen, toggleQr] = useToggle(); + const [favorites, toggleFavorite] = useFavorites(STORE_FAVS); + const [{ balanceTotal }, setBalances] = useState({ accounts: {} }); + const [filterOn, setFilter] = useState(''); + const [sortedAccountsWithDelegation, setSortedAccountsWithDelegation] = useState(); + const [{ sortedAccounts, sortedAddresses }, setSorted] = useState({ sortedAccounts: [], sortedAddresses: [] }); + const delegations = useCall(api.query.democracy?.votingOf?.multi, [sortedAddresses]); + const proxies = useCall<[ProxyDefinition[], BN][]>(api.query.proxy?.proxies.multi, [sortedAddresses], { + transform: (result: [([AccountId, ProxyType] | ProxyDefinition)[], BN][]): [ProxyDefinition[], BN][] => + api.tx.proxy.addProxy.meta.args.length === 3 + ? result as [ProxyDefinition[], BN][] + : (result as [[AccountId, ProxyType][], BN][]).map(([arr, bn]): [ProxyDefinition[], BN] => + [arr.map(([delegate, proxyType]): ProxyDefinition => api.createType('ProxyDefinition', { delegate, proxyType })), bn] + ) + }); + const isLoading = useLoadingDelay(); + + const headerRef = useRef([ + [t('Experience goods id'), 'start', 2], + [t('AppId'), 'start'], + [t('accounts'), 'start'], + [t('List period'), 'start'], + [t('Ranking'), 'start'], + [t('state'), 'start'], + [t('Review winner'), 'expand'], + [t('Knowledge power (kp)'), 'expand'], + [], + [], + [], + ]); + + useEffect((): void => { + + const sortedAccounts = sortAccounts(allAccounts, favorites); + const sortedAddresses = sortedAccounts.map((a) => a.account.address); + console.log("sortedAccounts:"+JSON.stringify(sortedAccounts)); + console.log("sortedAccountsWithDelegation:"+JSON.stringify(sortedAccountsWithDelegation)); + + setSorted({ sortedAccounts, sortedAddresses }); + }, [allAccounts, favorites]); + + useEffect(() => { + console.log("delegations:"+delegations) + if (api.query.democracy?.votingOf && !delegations?.length) { + return; + } + + setSortedAccountsWithDelegation( + sortedAccounts?.map((account, index) => { + let delegation: Delegation | undefined; + console.log("delegations2:"+delegations) + if (delegations && delegations[index]?.isDelegating) { + const { balance: amount, conviction, target } = delegations[index].asDelegating; + + delegation = { + accountDelegated: target.toString(), + amount, + conviction + }; + } + console.log("sortedAccountsWithDelegation2:"+JSON.stringify(sortedAccountsWithDelegation)); + return ({ + ...account, + delegation + }); + }) + ); + }, [api, delegations, sortedAccounts]); + + const _setBalance = useCallback( + (account: string, balance: BN) => + setBalances(({ accounts }: Balances): Balances => { + accounts[account] = balance; + console.log("balance:"+balance) + console.log(" accounts[account]:"+ accounts[account]) + return { + accounts, + balanceTotal: Object.values(accounts).reduce((total: BN, value: BN) => total.add(value), BN_ZERO) + }; + }), + [] + ); + + const footer = useMemo(() => ( + + + + + + + + + + + + + + + + ), [balanceTotal]); + + const filter = useMemo(() => ( +
+ ('filter by name or tags')} + onChange={setFilter} + value={filterOn} + /> +
+ ), [filterOn, t]); + + return ( +
+ ("You don't have any accounts. Some features are currently hidden and will only become available once you have accounts.")} + filter={filter} + footer={footer} + header={headerRef.current} + > + {!isLoading && sortedAccountsWithDelegation?.map(({ account, delegation, isFavorite }, index): React.ReactNode => ( + + ))} +
+
+ ); +} + +export default React.memo(styled(Overview)` + .filter--tags { + .ui--Dropdown { + padding-left: 0; + + label { + left: 1.55rem; + } + } + } +`); diff --git a/packages/page-competitive-list/src/WholeNetworkList/types.ts b/packages/page-competitive-list/src/WholeNetworkList/types.ts new file mode 100644 index 000000000000..b5d7c0b2aea1 --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/types.ts @@ -0,0 +1,31 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { KeyringAddress } from '@polkadot/ui-keyring/types'; + +import { WithTranslation } from 'react-i18next'; + +export { AppProps as ComponentProps } from '@polkadot/react-components/types'; + +export interface BareProps { + className?: string; +} + +export interface I18nProps extends BareProps, WithTranslation {} + +export interface ModalProps { + onClose: () => void; + onStatusChange: (status: ActionStatus) => void; +} + +export interface SortedAccount { + account: KeyringAddress; + children: SortedAccount[]; + isFavorite: boolean; +} + +export interface AmountValidateState { + error: string | null; + warning: string | null; +} diff --git a/packages/page-competitive-list/src/WholeNetworkList/useKnownAddresses.ts b/packages/page-competitive-list/src/WholeNetworkList/useKnownAddresses.ts new file mode 100644 index 000000000000..3a6d5210b0b0 --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/useKnownAddresses.ts @@ -0,0 +1,15 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { useMemo } from 'react'; +import { useAccounts, useAddresses } from '@polkadot/react-hooks'; + +export default function useKnownAddresses (exclude?: string): string[] { + const { allAccounts } = useAccounts(); + const { allAddresses } = useAddresses(); + + return useMemo( + () => [...allAccounts, ...allAddresses].filter((a) => a !== exclude), + [allAccounts, allAddresses, exclude] + ); +} diff --git a/packages/page-competitive-list/src/WholeNetworkList/useMultisigApprovals.ts b/packages/page-competitive-list/src/WholeNetworkList/useMultisigApprovals.ts new file mode 100644 index 000000000000..86ec28411ebe --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/useMultisigApprovals.ts @@ -0,0 +1,50 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { H256, Multisig } from '@polkadot/types/interfaces'; + +import { useContext, useEffect, useState } from 'react'; +import { useApi, useIncrement, useIsMountedRef } from '@polkadot/react-hooks'; +import { EventsContext } from '@polkadot/react-query'; +import { Option, StorageKey } from '@polkadot/types'; + +export default function useMultisigApprovals (address: string): [H256, Multisig][] { + const events = useContext(EventsContext); + const { api } = useApi(); + const [multiInfos, setMultiInfos] = useState<[H256, Multisig][]>([]); + const [trigger, incTrigger] = useIncrement(); + const mountedRef = useIsMountedRef(); + + // increment the trigger by looking at all events + // - filter the by multisig module (old utility is not supported) + // - find anything data item where the type is AccountId + // - increment the trigger when at least one matches our address + useEffect((): void => { + events + .filter(({ record: { event: { section } } }) => section === 'multisig') + .reduce((hasMultisig: boolean, { record: { event: { data } } }) => + data.reduce((hasMultisig: boolean, item) => + hasMultisig || (item.toRawType() === 'AccountId' && item.eq(address)), + hasMultisig), + false) && incTrigger(); + }, [address, events, incTrigger]); + + // query all the entries for the multisig, extracting approvals with their hash + useEffect((): void => { + const multiModule = api.query.multisig || api.query.utility; + + multiModule && multiModule.multisigs && + multiModule.multisigs + .entries(address) + .then((infos: [StorageKey, Option][]): void => { + mountedRef.current && setMultiInfos( + infos + .filter(([, opt]) => opt.isSome) + .map(([key, opt]) => [key.args[1] as H256, opt.unwrap()]) + ); + }) + .catch(console.error); + }, [address, api, mountedRef, trigger]); + + return multiInfos; +} diff --git a/packages/page-competitive-list/src/WholeNetworkList/useProxies.ts b/packages/page-competitive-list/src/WholeNetworkList/useProxies.ts new file mode 100644 index 000000000000..ae2ce60e6023 --- /dev/null +++ b/packages/page-competitive-list/src/WholeNetworkList/useProxies.ts @@ -0,0 +1,75 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { AccountId, BalanceOf, ProxyDefinition, ProxyType } from '@polkadot/types/interfaces'; +import { ITuple } from '@polkadot/types/types'; + +import BN from 'bn.js'; +import { useEffect, useState } from 'react'; +import { useAccounts, useApi, useIsMountedRef } from '@polkadot/react-hooks'; +import { Vec } from '@polkadot/types'; +import { BN_ZERO } from '@polkadot/util'; + +interface Proxy { + address: string; + delay: BN; + isOwned: boolean; + type: ProxyType; +} + +interface State { + hasOwned: boolean; + owned: Proxy[]; + proxies: Proxy[]; +} + +const EMPTY_STATE: State = { + hasOwned: false, + owned: [], + proxies: [] +}; + +function createProxy (allAccounts: string[], delegate: AccountId, type: ProxyType, delay = BN_ZERO): Proxy { + const address = delegate.toString(); + + return { + address, + delay, + isOwned: allAccounts.includes(address), + type + }; +} + +export default function useProxies (address?: string | null): State { + const { api } = useApi(); + const { allAccounts } = useAccounts(); + const mountedRef = useIsMountedRef(); + const [known, setState] = useState(EMPTY_STATE); + + useEffect((): void => { + setState(EMPTY_STATE); + + address && api.query.proxy && + api.query.proxy + .proxies | ProxyDefinition>, BalanceOf]>>(address) + .then(([_proxies]): void => { + const proxies = api.tx.proxy.addProxy.meta.args.length === 3 + ? (_proxies as ProxyDefinition[]).map(({ delay, delegate, proxyType }) => + createProxy(allAccounts, delegate, proxyType, delay) + ) + : (_proxies as [AccountId, ProxyType][]).map(([delegate, proxyType]) => + createProxy(allAccounts, delegate, proxyType) + ); + const owned = proxies.filter(({ isOwned }) => isOwned); + + mountedRef.current && setState({ + hasOwned: owned.length !== 0, + owned, + proxies + }); + }) + .catch(console.error); + }, [allAccounts, api, address, mountedRef]); + + return known; +} diff --git a/packages/page-competitive-list/src/index.tsx b/packages/page-competitive-list/src/index.tsx new file mode 100644 index 000000000000..8664519b8f00 --- /dev/null +++ b/packages/page-competitive-list/src/index.tsx @@ -0,0 +1,65 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { AppProps as Props } from '@polkadot/react-components/types'; + +import React, { useRef } from 'react'; +import { Route, Switch } from 'react-router'; +import { useAccounts, useIpfs } from '@polkadot/react-hooks'; +import { HelpOverlay, Tabs } from '@polkadot/react-components'; + +import basicMd from './md/basic.md'; +import { useTranslation } from './translate'; +import useCounter from './useCounter'; +import WholeNetworkList from './WholeNetworkList'; +import GoodsModelList from './GoodsModelList'; + +export { useCounter }; + +const HIDDEN_ACC = ['vanity']; + +function AccountsApp ({ basePath, onStatusChange }: Props): React.ReactElement { + const { t } = useTranslation(); + const { hasAccounts } = useAccounts(); + const { isIpfs } = useIpfs(); + + const itemsRef = useRef([ + { + name: 'wholeNetworkList', + text: t('Whole network list') + }, + { + name: 'goodsModelList', + text: t('Goods model list') + } + ]); + + return ( +
+ +
+
+ + + + + + + + +
+ ); +} + +export default React.memo(AccountsApp); diff --git a/packages/page-competitive-list/src/md/basic.md b/packages/page-competitive-list/src/md/basic.md new file mode 100644 index 000000000000..e46124702200 --- /dev/null +++ b/packages/page-competitive-list/src/md/basic.md @@ -0,0 +1,51 @@ +# Account + +An account is identified by its public address on the network. It is totally fine to give away this address, this is also the only information needed to receive funds. The network will **not** know about the name you give to this account in this application. + +# Balances + +The balances for each account is broken down into a number of areas, giving an overview of the totals, transferable and bonded funds as well as the funds currently being unbonded or redeemable. These are - + +- **total**: The overall amount of funds in the account, this includes the vested balance, available for transfer and locked. +- **available**: The funds that can be transferred or bonded, i.e. the funds that are available for any transaction. +- **bonded**: The funds bonded for validating or nominating. They are locked and cannot be transferred, although it can be unlocked for future actions. +- **redeemable**: The funds that can get redeemed, e.g made available for withdrawal, by clicking on the "lock" icon. +- **unbonding**: The funds that are being unbonded. The funds will be redeemable after the bonding period has passed. These funds can still be slashed. The information icon tells the amount of blocks left before the funds can be redeemed. + +# Security + +The public address is generated from a private key, itself generated from a seed or a mnemonic phrase. The seed or the mnemonic phrase should **never be shared with anybody** as they give access to your funds. It must be stored securely. +The password needed to create an account is used to encrypt your private key. You must choose a strong and unique password. +This password is also used to encrypt the private key in the backup file downloaded upon account creation. Thanks to this file together with your account password, you can recover your account. + +# Account recovery + +You can recover an account from its: +- seed or mnemonic: + Click on the "Add account" button, type your seed or mnemonic in the associated field. + +- backup file (also called JSON keystore file) and the account's password: + Click on "Restore JSON" button. Upload your backup file and type in the password associated. + +# Minimum allowed balance + +Accounts with a balance lower than the minimal amount, 100 milliUnits (miliDOTs for Polkadot) as of writing are considered as nonexistent for the network. If an account's balance ever drops below this amount, it is removed from the network. In this application, it will still be visible, but with a balance of 0. + +For a fund transfer to a **new account** (read an account with a balance of 0), if the amount transferred is less than the minimum allowed balance, then the transfer will "succeed" but the destination account will not be created (read its balance will remain 0); this essentially burns the transfer balance from the sender, because the receiver's balance never exceed the minimum allowed balance. + +If the receiver already exists (read it has a balance greater than 0), it is perfectly possible to transfer very low amounts. + +# Cryptography + +Substrate and Polkadot use Schnorrkel/Ristretto x25519 ("sr25519") as its key derivation and signing algorithm. + +Sr25519 is based on the same underlying Curve25519 as its EdDSA counterpart, Ed25519. However, it uses Schnorr signatures instead of the EdDSA scheme. Schnorr signatures bring some noticeable benefits over the ECDSA/EdDSA schemes. For one, it is more efficient and still retains the same feature set and security assumptions. Additionally, it allows for native multisignature through signature aggregation. + +If you wish to validate, the `session` account needs to use "ed25519" cryptography. + +# Contacts + +You can store and get quick access to the most commonly used address, such as a friends' account. +Any contact you create in this interface will be reflected in the application. + +You can edit the name of a contact by clicking on it. To remove a contact from the list, click on the trash icon to "Forget" it. diff --git a/packages/page-competitive-list/src/modals/Backup.tsx b/packages/page-competitive-list/src/modals/Backup.tsx new file mode 100644 index 000000000000..60f751e3e4b2 --- /dev/null +++ b/packages/page-competitive-list/src/modals/Backup.tsx @@ -0,0 +1,95 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import FileSaver from 'file-saver'; +import React, { useCallback, useState } from 'react'; +import { AddressRow, Button, Modal, Password } from '@polkadot/react-components'; +import keyring from '@polkadot/ui-keyring'; + +import { useTranslation } from '../translate'; + +interface Props { + onClose: () => void; + address: string; +} + +function Backup ({ address, onClose }: Props): React.ReactElement { + const { t } = useTranslation(); + const [isBusy, setIsBusy] = useState(false); + const [{ isPassTouched, password }, setPassword] = useState({ isPassTouched: false, password: '' }); + const [backupFailed, setBackupFailed] = useState(false); + const isPassValid = !backupFailed && keyring.isPassValid(password); + + const _onChangePass = useCallback( + (password: string): void => { + setBackupFailed(false); + setPassword({ isPassTouched: true, password }); + }, + [] + ); + + const _doBackup = useCallback( + (): void => { + setIsBusy(true); + setTimeout((): void => { + try { + const addressKeyring = address && keyring.getPair(address); + const json = addressKeyring && keyring.backupAccount(addressKeyring, password); + const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' }); + + FileSaver.saveAs(blob, `${address}.json`); + } catch (error) { + setBackupFailed(true); + setIsBusy(false); + console.error(error); + + return; + } + + setIsBusy(false); + onClose(); + }, 0); + }, + [address, onClose, password] + ); + + return ( + ('Backup account')} + > + + +

{t('An encrypted backup file will be created once you have pressed the "Download" button. This can be used to re-import your account on any other machine.')}

+

{t('Save this backup file in a secure location. Additionally, the password associated with this account is needed together with this backup file in order to restore your account.')}

+
+ ('The account password as specified when creating the account. This is used to encrypt the backup file and subsequently decrypt it when restoring the account.')} + isError={isPassTouched && !isPassValid} + label={t('password')} + onChange={_onChangePass} + onEnter={_doBackup} + tabIndex={0} + value={password} + /> +
+
+
+ +
+ ) + } + + + {infos && ( + !address || !raw)} + label={t('Set Subs')} + onStart={onClose} + params={[ + infos.map(([address, raw]) => [address, { raw }]) + ]} + tx='identity.setSubs' + /> + )} + + + ); +} + +export default React.memo(IdentitySubModal); diff --git a/packages/page-competitive-list/src/modals/Import.tsx b/packages/page-competitive-list/src/modals/Import.tsx new file mode 100644 index 000000000000..86516d00a6f5 --- /dev/null +++ b/packages/page-competitive-list/src/modals/Import.tsx @@ -0,0 +1,165 @@ +// Copyright 2017-2020 @polkadot/app-accounts authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { KeyringPair, KeyringPair$Json } from '@polkadot/keyring/types'; +import { ActionStatus } from '@polkadot/react-components/Status/types'; +import { ModalProps } from '../types'; + +import React, { useCallback, useMemo, useState } from 'react'; +import { AddressRow, Button, InputAddress, InputFile, Modal, Password } from '@polkadot/react-components'; +import { useApi } from '@polkadot/react-hooks'; +import { u8aToString } from '@polkadot/util'; +import keyring from '@polkadot/ui-keyring'; + +import { useTranslation } from '../translate'; +import ExternalWarning from './ExternalWarning'; + +interface Props extends ModalProps { + className?: string; + onClose: () => void; + onStatusChange: (status: ActionStatus) => void; +} + +interface PassState { + isPassValid: boolean; + password: string; +} + +const acceptedFormats = ['application/json', 'text/plain'].join(', '); + +function parseFile (file: Uint8Array, genesisHash?: string | null): KeyringPair | null { + try { + return keyring.createFromJson(JSON.parse(u8aToString(file)) as KeyringPair$Json, { genesisHash }); + } catch (error) { + console.error(error); + } + + return null; +} + +function Import ({ className = '', onClose, onStatusChange }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api, isDevelopment } = useApi(); + const [isBusy, setIsBusy] = useState(false); + const [pair, setPair] = useState(null); + const [{ isPassValid, password }, setPass] = useState({ isPassValid: false, password: '' }); + const apiGenesisHash = useMemo(() => isDevelopment ? null : api.genesisHash.toHex(), [api, isDevelopment]); + const differentGenesis = useMemo(() => pair?.meta.genesisHash && pair.meta.genesisHash !== apiGenesisHash, [apiGenesisHash, pair]); + + const _onChangeFile = useCallback( + (file: Uint8Array) => setPair(parseFile(file, apiGenesisHash)), + [apiGenesisHash] + ); + + const _onChangePass = useCallback( + (password: string) => setPass({ isPassValid: keyring.isPassValid(password), password }), + [] + ); + + const _onSave = useCallback( + (): void => { + if (!pair) { + return; + } + + setIsBusy(true); + setTimeout((): void => { + const status: Partial = { action: 'restore' }; + + try { + keyring.addPair(pair, password); + + status.status = 'success'; + status.account = pair.address; + status.message = t('account restored'); + + InputAddress.setLastValue('account', pair.address); + } catch (error) { + setPass((state: PassState) => ({ ...state, isPassValid: false })); + + status.status = 'error'; + status.message = (error as Error).message; + console.error(error); + } + + setIsBusy(false); + onStatusChange(status as ActionStatus); + + if (status.status !== 'error') { + onClose(); + } + }, 0); + }, + [onClose, onStatusChange, pair, password, t] + ); + + return ( + ('Add via backup file')} + size='large' + > + + + + + + + + + ('Select the JSON key file that was downloaded when you created the account. This JSON file contains your private key encrypted with your password.')} + isError={!pair} + label={t('backup file')} + onChange={_onChangeFile} + withLabel + /> + + +

{t('Supply a backed-up JSON file, encrypted with your account-specific password.')}

+
+
+ + + ('Type the password chosen at the account creation. It was used to encrypt your account\'s private key in the backup file.')} + isError={!isPassValid} + label={t('password')} + onChange={_onChangePass} + onEnter={_onSave} + value={password} + /> + + +

{t('The password previously used to encrypt this account.')}

+
+
+ { differentGenesis && +
+

{t('The network from which this account was originally generated is different than the network you are currently connected to. Once imported ensure you toggle the "allow on any network" option for the account to keep it visible on the current network.')}

+
+ } + +
+ +
+ + ); +} + +function NewProxy ({ index, onChangeAccount, onChangeType, onRemove, typeOpts, value: [accountId, type] }: NewProxyProps): React.ReactElement { + const { t } = useTranslation(); + + const _onChangeAccount = useCallback( + (value: string | null) => onChangeAccount(index, value), + [index, onChangeAccount] + ); + + const _onChangeType = useCallback( + (value?: number) => onChangeType(index, value), + [index, onChangeType] + ); + + const _onRemove = useCallback( + () => onRemove(index), + [index, onRemove] + ); + + return ( +
+
+ ('proxy account')} + onChange={_onChangeAccount} + type='account' + value={accountId} + /> + +
+
+
+
+ ); +} + +function ProxyOverview ({ className, onClose, previousProxy, proxiedAccount }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api } = useApi(); + const [batchStackPrevious, setBatchStackPrevious] = useState[]>([]); + const [batchStackAdded, setBatchStackAdded] = useState[]>([]); + const [extrinsic, setExtrinsic] = useState | null>(null); + const [previousProxyDisplay, setPreviousProxyDisplay] = useState( + previousProxy?.[0].map(({ delegate, proxyType }): [AccountId, ProxyType] => [delegate, proxyType]) || undefined + ); + const [addedProxies, setAddedProxies] = useState([]); + + useEffect(() => { + if (batchStackPrevious.length || batchStackAdded.length) { + setExtrinsic(() => createExtrinsic(api, batchStackPrevious, batchStackAdded)); + } + }, [api, batchStackPrevious, batchStackAdded]); + + const typeOpts = useMemo( + () => registry.createType('ProxyType').defKeys.map((text, value) => ({ text, value })), + [] + ); + + const _addProxy = useCallback( + () => { + const newAccount = registry.createType('AccountId', proxiedAccount); + const newType = registry.createType('ProxyType', 0); + + setAddedProxies((prevState) => { + const newProxy: [AccountId, ProxyType] = [newAccount, newType]; + + return !prevState + ? [newProxy] + : [...prevState, newProxy]; + }); + + setBatchStackAdded((prev) => prev.concat(createAddProxy(api, newAccount, newType))); + }, + [api, proxiedAccount] + ); + + const _delProxy = useCallback( + (index: number) => { + setAddedProxies((addedProxies) => addedProxies.filter((_, i) => i !== index)); + setBatchStackAdded((batchStackAdded) => batchStackAdded.filter((_, i) => i !== index)); + }, + [] + ); + + const _delPrev = useCallback( + (accountId: AccountId, type: ProxyType, index: number) => { + setPreviousProxyDisplay((previousProxyDisplay) => previousProxyDisplay?.filter((_, i) => i !== index)); + setBatchStackPrevious((batchStackPrevious) => [...batchStackPrevious, createRmProxy(api, accountId, type)]); + }, + [api] + ); + + const _changeProxyAccount = useCallback( + (index: number, address: string | null) => { + const accountId = registry.createType('AccountId', address); + const oldType = addedProxies[index][1]; + + // update the UI with the new selected account + setAddedProxies((prevState) => { + const newState = [...prevState]; + + newState[index][0] = accountId; + + return newState; + }); + + // Then add the new extrinsic to the batch stack with the new account + setBatchStackAdded((batchStackAdded): SubmittableExtrinsic<'promise'>[] => { + const newBatchAdded = batchStackAdded.filter((_, i) => i !== index); + + newBatchAdded.push(createAddProxy(api, accountId, oldType)); + + return newBatchAdded; + }); + }, + [addedProxies, api] + ); + + const _changeProxyType = useCallback( + (index: number, newTypeNumber: number | undefined) => { + const newType = registry.createType('ProxyType', newTypeNumber); + const oldAccount = addedProxies[index][0]; + + // update the UI with the new selected type + setAddedProxies((prevState) => { + const newState = [...prevState]; + + newState[index][1] = newType; + + return newState; + }); + + // The Batch stack needs to be updated wit the new type + // First, delete the corresponding extrinsic in the batch stack + const newBatchStackAdded = batchStackAdded.filter((_, i) => i !== index); + + // Then add the new extrinsic to the batch stack with the new account + newBatchStackAdded.push(createAddProxy(api, oldAccount, newType)); + setBatchStackAdded(newBatchStackAdded); + }, + [addedProxies, api, batchStackAdded] + ); + + return ( + ('Proxy overview')} + size='large' + > + + + + ('proxied account')} + type='account' + value={proxiedAccount} + /> + + +

{t('Any account set as proxy will be able to perform actions in place of the proxied account')}

+
+
+ + + {previousProxyDisplay?.map((value, index) => ( + + ))} + {addedProxies.map((value, index) => ( + + ))} + +