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
@@ -0,0 +1,29 @@
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { styled } from '@pezkuwi/react-components';
interface Props {
color?: string;
className?: string;
}
function ChainColorIndicator ({ className, color }: Props): React.ReactElement<Props> {
return (
<StyledDiv
className={className}
color={color}
/>
);
}
const StyledDiv = styled.div(({ color }: Props): string => `
background-color: ${color || 'white'} !important;
width: 100px;
flex: 1;
border-radius: 4px;
`);
export default React.memo(ChainColorIndicator);
@@ -0,0 +1,114 @@
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { MetadataDef } from '@pezkuwi/extension-inject/types';
import type { HexString } from '@pezkuwi/util/types';
import type { ChainInfo } from '../types.js';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { knownExtensions } from '@pezkuwi/apps-config';
import { externalEmptySVG } from '@pezkuwi/apps-config/ui/logos/external';
import { Button, Dropdown, Spinner, styled, Table } from '@pezkuwi/react-components';
import { useToggle } from '@pezkuwi/react-hooks';
import { objectSpread } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
import useExtensions from '../useExtensions.js';
import iconOption from './iconOption.js';
interface Props {
chainInfo: ChainInfo | null;
className?: string;
rawMetadata: HexString | null;
}
function Extensions ({ chainInfo, className, rawMetadata }: Props): React.ReactElement<Props> {
const isMetadataReady = rawMetadata !== null;
const { t } = useTranslation();
const { extensions } = useExtensions();
const [selectedIndex, setSelectedIndex] = useState(0);
const [isBusy, toggleBusy] = useToggle(true);
useEffect((): void => {
if (isMetadataReady) {
toggleBusy();
}
}, [isMetadataReady, toggleBusy]);
const options = useMemo(
() => (extensions || []).map(({ extension: { name, version } }, value) =>
iconOption(`${name} ${version}`, value, knownExtensions[name]?.ui.logo || externalEmptySVG)
),
[extensions]
);
const _updateMeta = useCallback(
(): void => {
if (chainInfo && extensions?.[selectedIndex]) {
toggleBusy();
const rawDef: MetadataDef = objectSpread<MetadataDef>({}, { ...chainInfo, rawMetadata });
extensions[selectedIndex]
.update(rawDef)
.catch(() => false)
.then(() => toggleBusy())
.catch(console.error);
}
},
[chainInfo, extensions, rawMetadata, selectedIndex, toggleBusy]
);
const headerRef = useRef<[React.ReactNode?, string?, number?][]>([
[t('Extensions'), 'start']
]);
return (
<StyledTable
className={className}
empty={t('No Upgradable extensions')}
header={headerRef.current}
>
{extensions
? options.length !== 0 && (
<>
<tr className='isExpanded isFirst'>
<td>
<Dropdown
label={t('upgradable extensions')}
onChange={setSelectedIndex}
options={options}
value={selectedIndex}
/>
</td>
</tr>
<tr className='isExpanded isLast'>
<td>
<Button.Group>
<Button
icon='upload'
isDisabled={isBusy}
label={t('Update metadata')}
onClick={_updateMeta}
/>
</Button.Group>
</td>
</tr>
</>
)
: <Spinner />
}
</StyledTable>
);
}
const StyledTable = styled(Table)`
table {
overflow: visible;
z-index: 2;
}
`;
export default React.memo(Extensions);
@@ -0,0 +1,308 @@
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BlockNumber, RuntimeVersion } from '@pezkuwi/types/interfaces';
import type { NetworkSpecsStruct } from '@pezkuwi/ui-settings/types';
import type { ChainInfo, ChainType } from '../types.js';
import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react';
import { ChainImg, Input, QrNetworkSpecs, Spinner, styled, Table } from '@pezkuwi/react-components';
import { useApi, useCall, useDebounce } from '@pezkuwi/react-hooks';
import { formatNumber } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
import ChainColorIndicator from './ChainColorIndicator.js';
interface Props {
chainInfo: ChainInfo | null;
className?: string;
}
// TODO-MOONBEAM: update NetworkSpecsStruct in @pezkuwi/ui-settings/types
interface NetworkSpecsStructWithType extends NetworkSpecsStruct{
chainType: ChainType
}
function getRandomColor (): string {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
const initialState = {
chainType: 'bizinikiwi' as ChainType,
color: '#FFFFFF',
decimals: 0,
genesisHash: '',
prefix: 0,
title: '',
unit: 'UNIT'
};
function NetworkSpecs ({ chainInfo, className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api, isApiReady, systemChain } = useApi();
const [qrData, setQrData] = useState<NetworkSpecsStructWithType>(initialState);
const debouncedQrData = useDebounce(qrData, 500);
const runtimeVersion = useCall<RuntimeVersion>(isApiReady && api.rpc.state.subscribeRuntimeVersion);
const blockNumber = useCall<BlockNumber>(isApiReady && api.derive.chain.bestNumber);
const reducer = (state: NetworkSpecsStructWithType, delta: Partial<NetworkSpecsStructWithType>): NetworkSpecsStructWithType => {
const newState = {
...state,
...delta
};
setQrData(newState);
return newState;
};
const [networkSpecs, setNetworkSpecs] = useReducer(reducer, initialState);
useEffect((): void => {
chainInfo && setNetworkSpecs({
chainType: chainInfo.chainType,
color: chainInfo.color || getRandomColor(),
decimals: chainInfo.tokenDecimals,
genesisHash: chainInfo.genesisHash,
prefix: chainInfo.ss58Format,
title: systemChain,
unit: chainInfo.tokenSymbol
});
}, [chainInfo, systemChain]);
const _onChangeColor = useCallback(
(color: string): void => setNetworkSpecs({ color }),
[]
);
const _onSetRandomColor = useCallback(
(event: React.MouseEvent<unknown>): void => {
event.preventDefault();
event.stopPropagation();
setNetworkSpecs({ color: getRandomColor() });
},
[]
);
const _checkColorValid = useCallback(
(): boolean => /^#[\da-fA-F]{6}|#[\da-fA-F]{3}$/.test(networkSpecs.color),
[networkSpecs]
);
const headerRef = useRef<[React.ReactNode?, string?, number?][]>([
[t('chain specifications'), 'start', 2]
]);
if (!isApiReady) {
return <Spinner />;
}
return (
<StyledTable
className={className}
empty={t('No open tips')}
header={headerRef.current}
>
<tr>
<td>
<div className='settings--networkSpecs-name'>
<Input
className='full'
isDisabled
label={t('Network Name')}
value={networkSpecs.title}
/>
<ChainImg className='settings--networkSpecs-logo' />
</div>
</td>
<td rowSpan={9}>
{qrData.genesisHash && (
<QrNetworkSpecs
className='settings--networkSpecs-qr'
networkSpecs={debouncedQrData}
/>
)}
</td>
</tr>
<tr>
<td>
<div className='settings--networkSpecs-color'>
<div>
<Input
className='full settings--networkSpecs-colorInput'
isError={!_checkColorValid()}
label={t('Color')}
onChange={_onChangeColor}
value={networkSpecs.color}
/>
<a
className='settings--networkSpecs-colorChangeButton'
onClick={_onSetRandomColor}
>
{t('generate random color')}
</a>
</div>
<ChainColorIndicator
className='settings--networkSpecs-colorBar'
color={networkSpecs.color}
/>
</div>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Genesis Hash')}
value={networkSpecs.genesisHash}
/>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Unit')}
value={networkSpecs.unit}
/>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Address Prefix')}
value={networkSpecs.prefix.toString()}
/>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Decimals')}
value={networkSpecs.decimals.toString()}
/>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Chain Type')}
value={networkSpecs.chainType}
/>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Runtime Version')}
value={runtimeVersion ? `${runtimeVersion.specName.toString()}/${runtimeVersion.specVersion.toNumber()}` : ''}
/>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Current Block')}
value={blockNumber ? formatNumber(blockNumber) : ''}
/>
</td>
</tr>
</StyledTable>
);
}
const StyledTable = styled(Table)`
td {
padding: 0;
.input.ui--Input input {
border: none !important;
background: transparent;
}
}
@media (max-width: 900px) {
tr {
&:first-child {
display: flex;
flex-direction: column;
}
}
}
.settings--networkSpecs-name {
position: relative;
.settings--networkSpecs-logo {
height: 32px;
left: 12px;
position: absolute;
top: 1rem;
width: 32px;
}
}
.settings--networkSpecs-color {
position: relative;
> div:first-child {
display: flex;
.settings--networkSpecs-colorInput {
min-width: 124px;
}
.settings--networkSpecs-colorChangeButton {
user-select: none;
cursor: pointer;
background: transparent;
border: none;
outline: none;
align-self: flex-end;
padding-bottom: 0.9rem;
}
}
.settings--networkSpecs-colorBar {
border-radius: 50%;
border: 1px solid grey;
height: 32px;
left: 12px;
position: absolute;
top: 1rem;
width: 32px;
}
}
.settings--networkSpecs-qr {
margin: 0.25rem auto;
max-width: 15rem;
img {
border: 1px solid white;
}
}
`;
export default React.memo(NetworkSpecs);
@@ -0,0 +1,95 @@
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BareProps as Props } from '@pezkuwi/react-components/types';
import React, { useRef } from 'react';
import { packageInfo } from '@pezkuwi/apps-config';
import { Input, Spinner, styled, Table } from '@pezkuwi/react-components';
import { useApi } from '@pezkuwi/react-hooks';
import { useTranslation } from '../translate.js';
const appsVersion = `apps v${packageInfo.version.replace('-x', '')}`;
function SystemVersion ({ className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api, isApiReady, systemName, systemVersion } = useApi();
const headerRef = useRef<[React.ReactNode?, string?, number?][]>([
[t('system version'), 'start', 2]
]);
if (!isApiReady) {
return <Spinner />;
}
return (
<StyledTable
className={className}
empty={t('No version information available')}
header={headerRef.current}
>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Node version')}
value={systemName + ' v' + systemVersion}
/>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('API Version')}
value={`${api.libraryInfo.replace('@pezkuwi/api ', '')}`}
/>
</td>
</tr>
<tr>
<td>
<Input
className='full'
isDisabled
label={t('Apps Version')}
value={`${appsVersion.replace('apps ', '')}`}
/>
</td>
</tr>
</StyledTable>
);
}
const StyledTable = styled(Table)`
td {
padding: 0;
text-align: left;
.input.ui--Input input {
border: none !important;
background: transparent;
padding: 0;
margin-left: 0;
}
.ui--Labelled-content .ui.input > input {
padding-left: 0 !important;
}
.ui--Labelled:not(.isSmall) {
padding-left: 0;
}
.ui--Labelled > label, .ui--Labelled > .ui--Labelled-content {
left: 0 !important;
padding-left: 0;
}
}
`;
export default React.memo(SystemVersion);
@@ -0,0 +1,28 @@
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
interface Option {
text: React.ReactNode;
value: number | string;
}
export default function itemOption (label: string, value: string | number, img: string): Option {
return {
text: (
<div
className='ui--Dropdown-item'
key={value}
>
<img
alt={label}
className='ui--Dropdown-icon'
src={img}
/>
<div className='ui--Dropdown-name'>{label}</div>
</div>
),
value
};
}
@@ -0,0 +1,31 @@
// Copyright 2017-2025 @pezkuwi/app-settings authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { useApi } from '@pezkuwi/react-hooks';
import useChainInfo from '../useChainInfo.js';
import useRawMetadata from '../useRawMetadata.js';
import Extensions from './Extensions.js';
import NetworkSpecs from './NetworkSpecs.js';
import SystemVersion from './SystemVersion.js';
export default function Metadata (): React.ReactElement {
const { isDevelopment } = useApi();
const rawMetadata = useRawMetadata();
const chainInfo = useChainInfo();
return (
<>
{!isDevelopment && (
<Extensions
chainInfo={chainInfo}
rawMetadata={rawMetadata}
/>
)}
<NetworkSpecs chainInfo={chainInfo} />
<SystemVersion />
</>
);
}