mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-06-14 01:41:10 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# @pezkuwi/app-ranked
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-apps/issues",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-apps/tree/master/packages/page-ranked#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/app-ranked",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"directory": "packages/page-ranked",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-apps.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "0.168.2-4-x",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-dom": "*",
|
||||
"react-is": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-ranked authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletColl, PalletPoll } from './types.js';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { Route, Routes } from 'react-router';
|
||||
|
||||
import Referenda, { useCounter } from '@pezkuwi/app-referenda/Referenda';
|
||||
import { Tabs } from '@pezkuwi/react-components';
|
||||
|
||||
import Members from './Members/index.js';
|
||||
import { useTranslation } from './translate.js';
|
||||
import useMembers from './useMembers.js';
|
||||
|
||||
interface Props {
|
||||
basePath: string;
|
||||
className?: string;
|
||||
palletColl: PalletColl;
|
||||
palletPoll: PalletPoll;
|
||||
}
|
||||
|
||||
function App ({ basePath, className, palletColl, palletPoll }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const members = useMembers(palletColl);
|
||||
const refCount = useCounter(palletPoll);
|
||||
|
||||
const tabs = useMemo(
|
||||
() => [
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'overview',
|
||||
text: t('Overview')
|
||||
},
|
||||
{
|
||||
name: 'referenda',
|
||||
text: t('Referenda ({{count}})', { replace: { count: refCount || 0 } })
|
||||
}
|
||||
],
|
||||
[refCount, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<main className={className}>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={tabs}
|
||||
/>
|
||||
<Routes>
|
||||
<Route path={basePath}>
|
||||
<Route
|
||||
element={
|
||||
<Referenda
|
||||
members={members?.memberIds}
|
||||
palletReferenda={palletPoll}
|
||||
palletVote={palletColl}
|
||||
ranks={members?.memberRanks}
|
||||
/>
|
||||
}
|
||||
path='referenda'
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<Members members={members?.members} />
|
||||
}
|
||||
index
|
||||
/>
|
||||
</Route>
|
||||
</Routes>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(App);
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-ranked authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { FlagColor } from '@pezkuwi/react-components/types';
|
||||
import type { Member as MemberType } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { AddressSmall, Tag } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
value: MemberType;
|
||||
}
|
||||
|
||||
const COLOR_LST: FlagColor[] = ['grey', 'grey', 'yellow', 'orange', 'purple', 'blue', 'green', 'black'];
|
||||
const COLOR_DEF = COLOR_LST[COLOR_LST.length - 1];
|
||||
|
||||
function Member ({ className, value: { accountId, info: { rank } } }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<tr className={className}>
|
||||
<td className='address all relative'>
|
||||
<AddressSmall value={accountId} />
|
||||
<Tag
|
||||
className='absolute'
|
||||
color={COLOR_LST[rank.toNumber()] || COLOR_DEF}
|
||||
hover={t('Membership rank')}
|
||||
label={rank.toString()}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Member);
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Member as MemberType } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { CardSummary, SummaryBox } from '@pezkuwi/react-components';
|
||||
import { formatNumber } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
members?: MemberType[];
|
||||
}
|
||||
|
||||
function Summary ({ className, members }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SummaryBox className={className}>
|
||||
<CardSummary label={t('members')}>
|
||||
{members === undefined
|
||||
? <span className='--tmp'>99</span>
|
||||
: formatNumber(members.length)
|
||||
}
|
||||
</CardSummary>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Summary);
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-ranked authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Member as MemberType } from '../types.js';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Table } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Member from './Member.js';
|
||||
import Summary from './Summary.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
members?: MemberType[];
|
||||
}
|
||||
|
||||
function Members ({ className, members }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
|
||||
[t('members'), 'start']
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Summary members={members} />
|
||||
<Table
|
||||
className={className}
|
||||
empty={members && t('No members found')}
|
||||
header={headerRef.current}
|
||||
isSplit
|
||||
>
|
||||
{members?.map((a): React.ReactNode => (
|
||||
<Member
|
||||
key={a.accountId}
|
||||
value={a}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Members);
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-ranked authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Main from './App.js';
|
||||
|
||||
export { default as useCounter } from './useCounter.js';
|
||||
|
||||
interface Props {
|
||||
basePath: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function App ({ basePath, className }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<Main
|
||||
basePath={basePath}
|
||||
className={className}
|
||||
palletColl='rankedCollective'
|
||||
palletPoll='rankedPolls'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(App);
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-ranked authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useTranslation as useTranslationBase } from 'react-i18next';
|
||||
|
||||
export function useTranslation (): { t: (key: string, options?: { replace: Record<string, unknown> }) => string } {
|
||||
return useTranslationBase('app-ranked');
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-ranked authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletRankedCollectiveMemberRecord } from '@pezkuwi/types/lookup';
|
||||
|
||||
export type PalletColl = 'rankedCollective' | 'fellowshipCollective' | 'ambassadorCollective';
|
||||
|
||||
export type PalletPoll = 'rankedPolls' | 'fellowshipReferenda' | 'ambassadorReferenda';
|
||||
|
||||
export interface Member {
|
||||
accountId: string;
|
||||
info: PalletRankedCollectiveMemberRecord;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-ranked authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useCounterNamed } from '@pezkuwi/app-referenda/useCounter';
|
||||
import { createNamedHook } from '@pezkuwi/react-hooks';
|
||||
|
||||
function useCounterImpl (): number {
|
||||
return useCounterNamed('rankedPolls');
|
||||
}
|
||||
|
||||
export default createNamedHook('useCounter', useCounterImpl);
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-referenda authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Changes } from '@pezkuwi/react-hooks/useEventChanges';
|
||||
import type { StorageKey } from '@pezkuwi/types';
|
||||
import type { AccountId32, EventRecord } from '@pezkuwi/types/interfaces';
|
||||
import type { PalletColl } from './types.js';
|
||||
|
||||
import { createNamedHook, useApi, useEventChanges, useMapKeys } from '@pezkuwi/react-hooks';
|
||||
|
||||
const OPT_ID = {
|
||||
transform: (keys: StorageKey<[AccountId32]>[]): AccountId32[] =>
|
||||
keys.map(({ args: [id] }) => id)
|
||||
};
|
||||
|
||||
function filter (records: EventRecord[]): Changes<AccountId32> {
|
||||
const added: AccountId32[] = [];
|
||||
const removed: AccountId32[] = [];
|
||||
|
||||
records.forEach(({ event: { data: [id], method } }): void => {
|
||||
if (method === 'MemberAdded') {
|
||||
added.push(id as AccountId32);
|
||||
} else {
|
||||
removed.push(id as AccountId32);
|
||||
}
|
||||
});
|
||||
|
||||
return { added, removed };
|
||||
}
|
||||
|
||||
function useMemberIdsImpl (collective: PalletColl): AccountId32[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query[collective].members, [], OPT_ID);
|
||||
|
||||
return useEventChanges([
|
||||
api.events[collective].MemberAdded,
|
||||
api.events[collective].MemberRemoved
|
||||
], filter, startValue);
|
||||
}
|
||||
|
||||
export default createNamedHook('useMemberIds', useMemberIdsImpl);
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option } from '@pezkuwi/types';
|
||||
import type { AccountId32 } from '@pezkuwi/types/interfaces';
|
||||
import type { PalletRankedCollectiveMemberRecord } from '@pezkuwi/types/lookup';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { Member, PalletColl } from './types.js';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@pezkuwi/react-hooks';
|
||||
|
||||
import useMembersIds from './useMemberIds.js';
|
||||
|
||||
interface Result {
|
||||
memberIds: string[];
|
||||
memberRanks: BN[];
|
||||
members: Member[];
|
||||
}
|
||||
|
||||
const OPT_MEM = {
|
||||
transform: ([[ids], infos]: [[AccountId32[]], Option<PalletRankedCollectiveMemberRecord>[]]): Result => {
|
||||
const members = infos
|
||||
.map((info, i) => [info.unwrapOr(null), ids[i]])
|
||||
.filter((r): r is [PalletRankedCollectiveMemberRecord, AccountId32] => !!r[0])
|
||||
.sort(([a], [b]) => b.rank.cmp(a.rank))
|
||||
.map(([info, accountId]): Member => ({
|
||||
accountId: accountId.toString(),
|
||||
info
|
||||
}));
|
||||
|
||||
return {
|
||||
memberIds: members.map(({ accountId }) => accountId),
|
||||
memberRanks: members.map(({ info }) => info.rank),
|
||||
members
|
||||
};
|
||||
},
|
||||
withParamsTransform: true
|
||||
};
|
||||
|
||||
function useMembersImpl (collective: PalletColl): Result | undefined {
|
||||
const { api } = useApi();
|
||||
const ids = useMembersIds(collective);
|
||||
const result = useCall(ids && ids.length !== 0 && api.query[collective].members.multi, [ids], OPT_MEM);
|
||||
|
||||
return useMemo(
|
||||
() => ids && ids.length === 0
|
||||
? { memberIds: [], memberRanks: [], members: [] }
|
||||
: result,
|
||||
[ids, result]
|
||||
);
|
||||
}
|
||||
|
||||
export default createNamedHook('useMembers', useMembersImpl);
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../page-referenda/tsconfig.build.json" },
|
||||
{ "path": "../react-components/tsconfig.build.json" },
|
||||
{ "path": "../react-hooks/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user