mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-07-28 14:25:49 +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,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;
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user