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,47 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React, { useEffect, useState } from 'react';
import { MarkWarning, styled } from '@pezkuwi/react-components';
const BrowserCheckAlert: React.FC = () => {
const [isTargetBrowser, setIsTargetBrowser] = useState(false);
useEffect(() => {
const ua = navigator.userAgent;
// Detect Firefox
const firefoxMatch = ua.match(/Firefox\/(\d+\.\d+)/);
if (firefoxMatch) {
// Check for Firefox 145.0 specifically
if (ua.includes('145.0')) {
setIsTargetBrowser(true);
}
}
}, []);
if (!isTargetBrowser) {
return null;
}
return (
<StyledBanner
className='warning centered'
withIcon={false}
>
The app is having some trouble running on Firefox v145.0. To keep everything running smoothly, please upgrade Firefox to the latest version or try using a different browser.
</StyledBanner>
);
};
const StyledBanner = styled(MarkWarning)`
border: 1px solid #ffc107;
background: #ffc10720;
font-size: 1rem !important;
margin-bottom: 5rem !important;
`;
export default BrowserCheckAlert;
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { RouteProps } from '@pezkuwi/apps-routing/types';
import React from 'react';
import { Navigate } from 'react-router';
interface Props extends RouteProps {
missingApis?: (string | string[])[];
}
function NotFound ({ basePath, missingApis = [] }: Props): React.ReactElement {
console.log(`Redirecting from route "${basePath}" to "/explorer"${missingApis.length ? `, missing the following APIs: ${JSON.stringify(missingApis)}` : ''}`);
return (
<Navigate to='/explorer' />
);
}
export default React.memo(NotFound);
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ActionStatus } from '@pezkuwi/react-components/Status/types';
import type { EventRecord } from '@pezkuwi/types/interfaces';
import type { KeyringOptions } from '@pezkuwi/ui-keyring/options/types';
import React, { useEffect } from 'react';
import { Status as StatusDisplay } from '@pezkuwi/react-components';
import { useAccounts, useApi, useCall, useQueue } from '@pezkuwi/react-hooks';
import { stringToU8a } from '@pezkuwi/util';
import { xxhashAsHex } from '@pezkuwi/util-crypto';
import { useTranslation } from '../translate.js';
interface Props {
optionsAll?: KeyringOptions;
}
let prevEventHash: string;
function filterEvents (allAccounts: string[], t: (key: string, options?: { replace: Record<string, unknown> }) => string, optionsAll?: KeyringOptions, events?: EventRecord[]): ActionStatus[] | null {
const eventHash = xxhashAsHex(stringToU8a(JSON.stringify(events)));
if (!optionsAll || !events || eventHash === prevEventHash) {
return null;
}
prevEventHash = eventHash;
return events
.map(({ event: { data, method, section } }): ActionStatus | null => {
if (section === 'balances' && method === 'Transfer') {
const account = data[1].toString();
if (allAccounts.includes(account)) {
return {
account,
action: `${section}.${method}`,
message: t('transfer received'),
status: 'event'
};
}
} else if (section === 'democracy') {
const index = data[0].toString();
return {
action: `${section}.${method}`,
message: t('update on #{{index}}', {
replace: {
index
}
}),
status: 'event'
};
}
return null;
})
.filter((item): item is ActionStatus => !!item);
}
function Status ({ optionsAll }: Props): React.ReactElement<Props> {
const { queueAction } = useQueue();
const { api, isApiReady } = useApi();
const { allAccounts } = useAccounts();
const { t } = useTranslation();
const events = useCall<EventRecord[]>(isApiReady && api.query.system?.events);
useEffect((): void => {
const filtered = filterEvents(allAccounts, t, optionsAll, events);
filtered && queueAction(filtered);
}, [allAccounts, events, optionsAll, queueAction, t]);
return (
<StatusDisplay />
);
}
export default React.memo(Status);
+132
View File
@@ -0,0 +1,132 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Route } from '@pezkuwi/apps-routing/types';
import React, { Suspense, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import createRoutes from '@pezkuwi/apps-routing';
import { ErrorBoundary, Spinner, styled } from '@pezkuwi/react-components';
import { useApi, useQueue } from '@pezkuwi/react-hooks';
import { TabsCtx } from '@pezkuwi/react-hooks/ctx/Tabs';
import { findMissingApis } from '../endpoint.js';
import { useTranslation } from '../translate.js';
import BrowserCheckAlert from './BrowserCheckAlert.js';
import NotFound from './NotFound.js';
import Status from './Status.js';
interface Props {
className?: string;
}
const NOT_FOUND: Route = {
Component: NotFound,
display: {},
group: 'settings',
icon: 'times',
isIgnored: false,
name: 'unknown',
text: 'Unknown'
};
function Content ({ className }: Props): React.ReactElement<Props> {
const location = useLocation();
const { t } = useTranslation();
const { api, isApiConnected, isApiReady, isDevelopment } = useApi();
const { queueAction } = useQueue();
const { Component, display: { needsApi, needsApiCheck, needsApiInstances }, icon, name, text } = useMemo(
(): Route => {
const app = location.pathname.slice(1) || '';
return createRoutes(t).find((r) =>
r &&
app.startsWith(r.name) &&
(isDevelopment || !r.display.isDevelopment)
) || NOT_FOUND;
},
[isDevelopment, location, t]
);
const missingApis = useMemo(
() => needsApi
? isApiReady && isApiConnected
? findMissingApis(api, needsApi, needsApiInstances, needsApiCheck)
: null
: [],
[api, isApiConnected, isApiReady, needsApi, needsApiCheck, needsApiInstances]
);
return (
<StyledDiv className={className}>
{!missingApis
? (
<div className='connecting'>
<BrowserCheckAlert />
<Spinner label={t('Initializing connection')} />
</div>
)
: (
<>
<Suspense fallback='...'>
<ErrorBoundary trigger={name}>
<TabsCtx.Provider value={{ icon, text }}>
{missingApis.length
? (
<NotFound
basePath={`/${name}`}
location={location}
missingApis={missingApis}
onStatusChange={queueAction}
/>
)
: (
<Component
basePath={`/${name}`}
location={location}
onStatusChange={queueAction}
/>
)
}
</TabsCtx.Provider>
</ErrorBoundary>
</Suspense>
<Status />
</>
)
}
</StyledDiv>
);
}
const StyledDiv = styled.div`
flex-grow: 1;
overflow: hidden auto;
padding: 0 0 1rem 0;
position: relative;
width: 100%;
.connecting {
padding: 3.5rem 0;
}
& main > *:not(header):not(.hasOwnMaxWidth) {
max-width: var(--width-full);
margin-right: auto;
margin-left: auto;
width: 100%;
padding: 0 1.5rem;
@media only screen and (max-width: 1100px) {
padding: 0 1rem;
}
@media only screen and (max-width: 800px) {
padding: 0 0.75rem;
}
}
`;
export default React.memo(Content);