mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-06-12 17:01:09 +00:00
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:
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user