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
+164
View File
@@ -0,0 +1,164 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { IconName } from '@fortawesome/fontawesome-svg-core';
import React, { useCallback, useEffect } from 'react';
import { Button, Icon, styled } from '@pezkuwi/react-components';
import { useToggle } from '@pezkuwi/react-hooks';
interface Props {
children: React.ReactNode;
className?: string;
icon: IconName;
isBottom?: boolean;
isFull?: boolean;
type: 'error' | 'info';
isDev?: boolean;
}
function BaseOverlay ({ children, className = '', icon, isBottom = false, isDev, isFull = false, type }: Props): React.ReactElement<Props> | null {
const [isHidden, toggleHidden] = useToggle();
const checkLcValue = useCallback(() => {
if (isDev) {
localStorage.setItem('dev:notification', new Date().toString());
}
toggleHidden();
}, [isDev, toggleHidden]);
useEffect(() => {
const item = localStorage.getItem('dev:notification');
if (item) {
const date = new Date(item);
date.setMonth(date.getMonth() + 1);
// 1 month has passed - remove the localStorage
// and resume the notification
if (date.getTime() <= new Date().getTime()) {
localStorage.removeItem('dev:notification');
} else {
toggleHidden();
}
}
}, [toggleHidden]);
if (isHidden) {
return null;
}
return (
<StyledDiv className={`${className} ${type === 'error' ? 'isError' : 'isInfo'} ${isBottom ? 'isBottom' : 'isTop'} ${isFull ? 'isFull' : 'isPartial'}`}>
<div className='content'>
<Icon
className='contentIcon'
icon={icon}
size='2x'
/>
<div className='contentItem'>
{children}
</div>
<Button
className='closeIcon'
icon='times'
isBasic
isCircular
onClick={checkLcValue}
/>
</div>
</StyledDiv>
);
}
const StyledDiv = styled.div`
background: var(--bg-menu);
border: 1px solid transparent;
border-radius: 0.25rem;
border-left-width: 0.25rem;
line-height: 1.5em;
padding: 0 1rem;
position: fixed;
right: 0.75rem;
top: 0.75rem;
z-index: 500;
&.isBottom {
position: static;
z-index: 0;
}
&.isFull {
left: 0.75rem;
}
&.isPartial {
max-width: 42rem;
width: 42rem;
.content {
max-width: 50rem;
}
}
&:before {
border-radius: 0.25rem;
bottom: 0;
content: ' ';
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: -1;
}
&.isError {
&:before {
background: rgba(255, 12, 12, 0.05);
}
border-color: rgba(255, 12, 12, 1);
}
&.isInfo {
&:before {
background: rgba(255, 196, 12, 0.05);
}
border-color: rgba(255, 196, 12, 1);
}
.content {
align-items: center;
display: flex;
margin: 0 auto;
padding: 1em 3rem 1rem 0.5rem;
position: relative;
.contentIcon {
flex: 0;
}
.contentItem {
flex: 1;
padding: 0 1rem;
> div+div {
margin-top: 0.5rem;
}
}
}
.closeIcon {
cursor: pointer;
position: absolute;
right: 0em;
top: 0.75rem;
}
`;
export default React.memo(BaseOverlay);
+64
View File
@@ -0,0 +1,64 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { Spinner, styled } from '@pezkuwi/react-components';
import GlobalStyle from '@pezkuwi/react-components/styles';
import { useTheme } from '@pezkuwi/react-hooks';
import { useTranslation } from '../translate.js';
import BaseOverlay from './Base.js';
const BeforeApiInit = () => {
const { themeClassName } = useTheme();
const { t } = useTranslation();
return (
<>
<GlobalStyle />
<StyledDiv className={` apps--Wrapper ${themeClassName}`}>
<BaseOverlay
icon='globe'
type='info'
>
<div>{t('Waiting to establish a connection with the remote endpoint.')}</div>
</BaseOverlay>
<div className='connecting'>
<Spinner label='Initializing connection' />
</div>
<div id={'portals'} />
</StyledDiv>
</>
);
};
const StyledDiv = styled.div`
background: var(--bg-page);
box-sizing: border-box;
display: flex;
flex-direction: column;
min-height: 100vh;
.connecting {
padding-block: calc(3.5rem + 56px);
}
${[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24
].map((n) => `
.greyAnim-${n} {
animation: greyAnim${n} 2s;
}
@keyframes greyAnim${n} {
0% { background: #a6a6a6; }
50% { background: darkorange; }
100% { background: #a6a6a6; }
}
`).join('')}
`;
export default BeforeApiInit;
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { styled } from '@pezkuwi/react-components';
import DotApps from './DotApps.js';
import LocalFork from './LocalFork.js';
interface Props {
className?: string;
}
function Bottom ({ className }: Props): React.ReactElement<Props> | null {
return (
<StyledDiv className={className}>
<LocalFork />
<DotApps />
</StyledDiv>
);
}
const StyledDiv = styled.div`
position: fixed;
bottom: 0.75rem;
right: 0.75rem;
left: 0.75rem;
top: auto;
padding: 1rem;
z-index: 500;
display: flex;
flex-direction: column;
row-gap: 0.75rem;;
div.isInfo:before {
content: none;
}
`;
export default React.memo(Bottom);
+81
View File
@@ -0,0 +1,81 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { useApi } from '@pezkuwi/react-hooks';
import { settings } from '@pezkuwi/ui-settings';
import { useTranslation } from '../translate.js';
import BaseOverlay from './Base.js';
const wsUrl = settings.apiType.param;
const isWs = settings.apiType.type === 'json-rpc' && typeof wsUrl === 'string' && wsUrl.startsWith('ws://');
const isWsLocal = typeof wsUrl === 'string' && wsUrl.includes('127.0.0.1');
const isHttps = window.location.protocol.startsWith('https:');
interface Props {
className?: string;
}
function Connecting ({ className }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { apiError, isApiConnected, isApiReady, isWaitingInjected } = useApi();
if (apiError) {
return (
<BaseOverlay
className={className}
icon='globe'
type='error'
>
<div>{apiError}</div>
</BaseOverlay>
);
} else if (!isApiReady) {
return (
<BaseOverlay
className={className}
icon='globe'
type='info'
>
<div>
{
isApiConnected
? t('Waiting to complete metadata retrieval from remote endpoint.')
: t('Waiting to establish a connection with the remote endpoint.')
}
</div>
</BaseOverlay>
);
} else if (isWaitingInjected) {
return (
<BaseOverlay
className={className}
icon='puzzle-piece'
type='info'
>
<div>{t('Waiting for authorization from the extension. Please open the installed extension and approve or reject access.')}</div>
</BaseOverlay>
);
} else if (!isApiConnected) {
return (
<BaseOverlay
className={className}
icon='globe'
type='error'
>
<div>{t('You are not connected to a node. Ensure that your node is running and that the Websocket endpoint is reachable.')}</div>
{
isWs && !isWsLocal && isHttps
? <div>{t('You are connecting from a secure location to an insecure WebSocket ({{wsUrl}}). Due to browser mixed-content security policies this connection type is not allowed. Change the RPC service to a secure \'wss\' endpoint.', { replace: { wsUrl } })}</div>
: undefined
}
</BaseOverlay>
);
}
return null;
}
export default React.memo(Connecting);
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React, { useMemo } from 'react';
import { createWsEndpoints } from '@pezkuwi/apps-config/endpoints';
import { useApi } from '@pezkuwi/react-hooks';
import { useTranslation } from '../translate.js';
import BaseOverlay from './Base.js';
const isDev = window.location.host.startsWith('pezkuwi.js.org');
const dnsLinks = createWsEndpoints(() => '')
.map((e) => e.dnslink)
.reduce((all: string[], dnslink) => {
if (dnslink && !all.includes(dnslink)) {
all.push(dnslink);
}
return all;
}, []);
interface Props {
className?: string;
}
function DotApps ({ className }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { systemChain } = useApi();
const appsUrl = useMemo(
() => {
const lowerChain = systemChain?.toLowerCase();
return (lowerChain && dnsLinks.includes(lowerChain))
? `https://${lowerChain}.dotapps.io`
: 'https://dotapps.io';
},
[systemChain]
);
if (isDev) {
return (
<BaseOverlay
className={className}
icon='link'
isBottom
isDev
isFull
type='info'
>
<div>
{t('You are connected to the development instance of the UI. For a fully decentralized experience, you are encouraged to use the IPFS deployed version as the canonical URL: ')}
<a
href={appsUrl}
rel='noreferrer'
target='_blank'
>
{appsUrl.replace('https://', '')}
</a>
</div>
</BaseOverlay>
);
}
return null;
}
export default React.memo(DotApps);
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2017-2025 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { useApi } from '@pezkuwi/react-hooks';
import { useTranslation } from '../translate.js';
import BaseOverlay from './Base.js';
interface Props {
className?: string;
}
function LocalFork ({ className }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { isLocalFork } = useApi();
if (isLocalFork) {
return (
<BaseOverlay
className={className}
icon='link'
isBottom
isFull
type='info'
>
<div>
{t('Local fork powered by ')}
<a
href='https://github.com/AcalaNetwork/chopsticks'
rel='noreferrer'
target='_blank'
>
Chopsticks
</a>
.
</div>
</BaseOverlay>
);
}
return null;
}
export default React.memo(LocalFork);