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:
2026-01-07 13:05:27 +03:00
commit d21bfb1320
5867 changed files with 329019 additions and 0 deletions
View File
View File
+1
View File
@@ -0,0 +1 @@
# @pezkuwi/app-ranked
+23
View File
@@ -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": "*"
}
}
+74
View File
@@ -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);
+26
View File
@@ -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);
+8
View File
@@ -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');
}
+13
View File
@@ -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;
}
+11
View File
@@ -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);
+41
View File
@@ -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);
+55
View File
@@ -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);
+13
View File
@@ -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" }
]
}