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,126 @@
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ChainInformation } from '@pezkuwi/react-hooks/types';
import type { ActiveFilters } from '../types.js';
import React, { useCallback, useState } from 'react';
import { Button, Dropdown, Input } from '@pezkuwi/react-components';
import { useTranslation } from '../translate.js';
import { FilterType, useBlocksSort, useSearchFilter, useTypeFilter } from './filters/index.js';
interface Props {
chainInfo: Record<number, ChainInformation>;
data: number[];
onFilter: (data: number[]) => void;
}
function Filters ({ chainInfo, data: initialData, onFilter }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [activeFilters, setActiveFilters] = useState<ActiveFilters>({
search: [],
type: []
});
const { apply: applyBlocksSort, direction, onApply: onApplySort, reset: resetSort } = useBlocksSort({
chainInfo,
data: initialData,
onFilter: (data) => handleFilter(data, FilterType.BLOCKS)
});
const { apply: applySearchFilter, onApply: onApplySearch, reset: resetSearch, searchValue } = useSearchFilter({
data: initialData,
onFilter: (data) => handleFilter(data, FilterType.SEARCH)
});
const { apply: applyTypeFilter, onApply: onApplyType, reset: resetType, selectedType, typeOptions } = useTypeFilter({
chainInfo,
data: initialData,
onFilter: (data) => handleFilter(data, FilterType.TYPE)
});
/**
* 1. Applies additional filtering already present in the filters
* 2. Performs filtering based on the filter type
*/
const handleFilter = useCallback((
filteredData: number[],
filterType: FilterType
): void => {
let resultData = filteredData;
if (filterType !== FilterType.SEARCH) {
resultData = applySearchFilter(resultData, activeFilters.search);
}
if (filterType !== FilterType.TYPE) {
resultData = applyTypeFilter(resultData, activeFilters.type);
}
if (filterType !== FilterType.BLOCKS && direction) {
resultData = applyBlocksSort(resultData, direction);
}
if (filterType !== FilterType.BLOCKS) {
setActiveFilters((prev) => ({
...prev,
[filterType]: filteredData.length === initialData.length ? [] : filteredData
}));
}
onFilter(resultData);
}, [initialData, onFilter, activeFilters, direction, applyBlocksSort, applyTypeFilter, applySearchFilter]);
const resetAllFilters = useCallback(() => {
resetSearch();
resetType();
resetSort();
setActiveFilters({ search: [], type: [] });
onFilter(initialData);
}, [initialData, onFilter, resetSearch, resetType, resetSort]);
const hasActiveFilters = searchValue || selectedType || direction;
return (
<div style={{ alignItems: 'center', display: 'flex', flexDirection: 'row', gap: '10px' }}>
<div style={{ minWidth: '250px' }}>
<Input
aria-label={t('Search by teyrchain id or name')}
className='full isSmall'
label={t('Search')}
onChange={onApplySearch}
placeholder={t('teyrchain id or name')}
value={searchValue}
/>
</div>
<Dropdown
className='isSmall'
label={t('type')}
onChange={onApplyType}
options={typeOptions}
placeholder='select type'
value={selectedType}
/>
<div style={{ height: '20px' }}>
<Button
icon={direction ? (direction === 'DESC' ? 'arrow-up' : 'arrow-down') : 'sort'}
label={t('blocks')}
onClick={onApplySort}
/>
</div>
{hasActiveFilters && (
<div style={{ height: '20px' }}>
<Button
icon='times'
label={t('Reset filters')}
onClick={resetAllFilters}
/>
</div>
)}
</div>
);
}
export default Filters;
@@ -0,0 +1,103 @@
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BrokerStatus, CoreDescription, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord, RegionInfo } from '@pezkuwi/react-hooks/types';
import type { RelayName } from '../types.js';
import React, { useMemo } from 'react';
import { CardSummary, SummaryBox } from '@pezkuwi/react-components';
import { BN } from '@pezkuwi/util';
import { useCoretimeContext } from '../CoretimeContext.js';
import { useTranslation } from '../translate.js';
import { FirstCycleStart } from '../utils/index.js';
interface Props {
coreDscriptors?: CoreDescription[];
saleInfo: PalletBrokerSaleInfoRecord
config: PalletBrokerConfigRecord,
region: RegionInfo[],
status: BrokerStatus,
teyrchainCount: number
relayName: RelayName,
}
function Summary ({ config, teyrchainCount, relayName, status }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { coretimeInfo, currentRegionEnd, currentRegionStart, saleEndDate, saleStartDate } = useCoretimeContext();
const saleNumber = useMemo(() => {
if (relayName && currentRegionEnd) {
return Math.floor(
(currentRegionEnd - FirstCycleStart.timeslice.coretime[relayName]) / config.regionLength
);
}
return undefined;
}, [currentRegionEnd, relayName, config]);
const timeslicesSinceCycleStart = useMemo(() => currentRegionEnd && new BN(config?.regionLength).sub((new BN(currentRegionEnd)).sub(new BN(status.lastTimeslice))), [status, config, currentRegionEnd]);
return (
<SummaryBox>
<section>
{status &&
<CardSummary label={t('sale number')}>
<div>
{saleNumber}
</div>
</CardSummary>
}
<CardSummary label={t('timeslice')}>
{status?.lastTimeslice}
</CardSummary>
<CardSummary label={t('teyrchains')}>
{teyrchainCount && teyrchainCount}
</CardSummary>
{config && status && currentRegionEnd && saleEndDate && saleStartDate && timeslicesSinceCycleStart && coretimeInfo?.constants &&
<>
<CardSummary
className='media--800'
label={t('timeslice progress')}
progress={{
hideGraph: true,
hideValue: false,
isBlurred: false,
total: new BN(config?.regionLength),
value: timeslicesSinceCycleStart,
withTime: false
}}
/>
<CardSummary
label={t('cycle')}
progress={{
total: new BN(config.regionLength).mul(new BN(coretimeInfo?.constants.relay.blocksPerTimeslice)),
value: timeslicesSinceCycleStart.mul(new BN(coretimeInfo?.constants.relay.blocksPerTimeslice)),
withTime: true
}}
/>
</>
}
</section>
<section className='media--1200'>
<CardSummary label={t('sale dates')}>
<div>
<div style={{ fontSize: '14px' }}>{saleStartDate}</div>
<div style={{ fontSize: '14px' }}>{saleEndDate}</div>
</div>
</CardSummary>
<CardSummary label={t('sale ts')}>
<div>
<div style={{ fontSize: '14px' }}>{currentRegionStart}</div>
<div style={{ fontSize: '14px' }}>{currentRegionEnd}</div>
</div>
</CardSummary>
</section>
</SummaryBox>
);
}
export default React.memo(Summary);
@@ -0,0 +1,7 @@
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from '../../types.js';
export * from './useBlockSort.js';
export * from './useSearchFilter.js';
export * from './useTypeFilter.js';
@@ -0,0 +1,56 @@
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ChainInformation } from '@pezkuwi/react-hooks/types';
import type { ChainInfoFilterProps, SortDirection } from '../../types.js';
import { useCallback, useState } from 'react';
export function sortByBlocks (data: number[], chainInfo: Record<number, ChainInformation>, direction: SortDirection): number[] {
if (!data || !chainInfo || !direction) {
return data || [];
}
const filteredData = data.filter((block) => !!chainInfo[block]?.workTaskInfo[0]);
return [...filteredData].sort((a, b) => {
const aInfo = chainInfo[a]?.workTaskInfo[0];
const bInfo = chainInfo[b]?.workTaskInfo[0];
return direction === 'DESC'
? bInfo.lastBlock - aInfo.lastBlock
: aInfo.lastBlock - bInfo.lastBlock;
});
}
const getNextSortState = (current: SortDirection): SortDirection =>
({ '': 'DESC', ASC: '', DESC: 'ASC' } as const)[current];
export function useBlocksSort ({ chainInfo, data, onFilter }: ChainInfoFilterProps) {
const [direction, setDirection] = useState<SortDirection>('');
const apply = useCallback((data: number[], sort: SortDirection): number[] => {
return sort
? sortByBlocks(data, chainInfo, sort)
: data;
}, [chainInfo]);
const onApply = useCallback(() => {
const nextDirection = getNextSortState(direction);
setDirection(nextDirection);
onFilter(nextDirection ? sortByBlocks(data, chainInfo, nextDirection) : data);
}, [data, chainInfo, onFilter, direction]);
const reset = useCallback(() => {
setDirection('');
onFilter(data || []);
}, [data, onFilter]);
return {
apply,
direction,
onApply,
reset
};
}
@@ -0,0 +1,78 @@
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React, { useCallback, useMemo, useState } from 'react';
import { useRelayEndpoints } from '@pezkuwi/react-hooks/useParaEndpoints';
interface UseSearchFilterProps {
data: number[];
onFilter: (data: number[]) => void;
}
export function useSearchFilter ({ data, onFilter }: UseSearchFilterProps) {
const [searchValue, setSearchValue] = useState('');
const endpoints = useRelayEndpoints();
const endPointsMap = useMemo(() =>
Object.fromEntries(
endpoints
.filter((e) => e?.text && e.paraId)
.map((e) => [
React.isValidElement(e.text) ? '' : String(e.text),
e.paraId
])
),
[endpoints]
);
const apply = useCallback((data: number[], activeSearch: number[]): number[] => {
return activeSearch.length > 0
? data.filter((id) => activeSearch.includes(id))
: data;
}, []);
const reset = useCallback(() => {
setSearchValue('');
onFilter(data);
}, [data, onFilter]);
const onInputChange = useCallback((v: string) => {
setSearchValue(v);
const searchLower = v.trim().toLowerCase();
if (!searchLower) {
onFilter(data);
return;
}
const matchingIds = new Set<number>();
for (const item of data) {
const itemStr = item.toString().toLowerCase();
if (itemStr.includes(searchLower)) {
matchingIds.add(item);
continue;
}
for (const [key, value] of Object.entries(endPointsMap)) {
if (key.toLowerCase().includes(searchLower) && value === item) {
matchingIds.add(item);
break;
}
}
}
const filteredData = Array.from(matchingIds);
onFilter(apply(data, filteredData));
}, [data, endPointsMap, onFilter, apply]);
return {
apply,
onApply: onInputChange,
reset,
searchValue
};
}
@@ -0,0 +1,76 @@
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { FlagColor } from '@pezkuwi/react-components/types';
import type { ChainInfoFilterProps } from '../../types.js';
import React, { useCallback, useState } from 'react';
import { Tag } from '@pezkuwi/react-components';
import { CoreTimeTypes } from '@pezkuwi/react-hooks/constants';
import { coretimeTypeColours } from '../../utils/index.js';
const coretimeTypes = Object.keys(CoreTimeTypes).filter((key) => isNaN(Number(key)));
const typeOptions = [
{
text: 'All',
value: 'All'
},
...coretimeTypes.map((type) => ({
text: (
<Tag
color={coretimeTypeColours[CoreTimeTypes[type as keyof typeof CoreTimeTypes]] as FlagColor}
label={type}
/>
),
value: CoreTimeTypes[type as keyof typeof CoreTimeTypes].toString()
}))
];
export function useTypeFilter ({ chainInfo, data, onFilter }: ChainInfoFilterProps) {
const [selectedType, setSelectedType] = useState<string>('');
const [activeType, setActiveType] = useState<number[]>([]);
const apply = useCallback((data: number[], activeType: number[]): number[] => {
return activeType.length > 0
? data.filter((id) => activeType.includes(id))
: data;
}, []);
const reset = useCallback(() => {
setSelectedType('');
setActiveType([]);
onFilter(data);
}, [data, onFilter]);
const onDropDownChange = useCallback((v: string) => {
setSelectedType(v);
if (!v || v === 'All') {
setActiveType([]);
onFilter(data);
return;
}
const filteredData = data.filter((paraId) => {
const taskInfo = chainInfo[paraId]?.workTaskInfo;
return taskInfo?.length > 0 && taskInfo[0].type.toString() === v;
});
setActiveType(filteredData);
onFilter(apply(data, filteredData));
}, [chainInfo, data, onFilter, apply]);
return {
activeType,
apply,
onApply: onDropDownChange,
reset,
selectedType,
typeOptions
};
}
@@ -0,0 +1,42 @@
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { RelayName } from '../types.js';
import React from 'react';
import { useCoretimeContext } from '../CoretimeContext.js';
import TeyrchainsTable from '../TeyrchainsTable.js';
import Summary from './Summary.js';
interface Props {
className?: string;
relayName: RelayName
}
function Overview ({ className, relayName }: Props): React.ReactElement<Props> {
const { coretimeInfo } = useCoretimeContext();
return (
<main className={className}>
{coretimeInfo && (
<Summary
config={coretimeInfo?.config}
teyrchainCount={coretimeInfo.taskIds?.length || 0}
region={coretimeInfo?.region}
relayName={relayName}
saleInfo={coretimeInfo?.salesInfo}
status={coretimeInfo?.status}
/>
)}
{!!coretimeInfo &&
<TeyrchainsTable
coretimeInfo={coretimeInfo}
relayName={relayName}
/>
}
</main>
);
}
export default React.memo(Overview);