mirror of
https://github.com/pezkuwichain/pezkuwi-sdk-ui.git
synced 2026-08-07 11:45:40 +00:00
Initial commit: Pezkuwi SDK UI
Comprehensive web interface for interacting with Pezkuwi blockchain. Features: - Blockchain explorer - Wallet management - Staking interface - Governance participation - Developer tools Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
// Copyright 2017-2026 @pezkuwi/app-settings authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ActionStatus } from '@pezkuwi/react-components/Status/types';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
import store from 'store';
|
||||
|
||||
import { decodeUrlTypes, encodeUrlTypes } from '@pezkuwi/react-api/urlTypes';
|
||||
import { Button, CopyButton, Editor, InputFile, styled } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import { isJsonObject, stringToU8a, u8aToString } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from './translate.js';
|
||||
|
||||
const EMPTY_CODE = '{\n\n}';
|
||||
const EMPTY_TYPES = {};
|
||||
|
||||
interface AllState {
|
||||
code: string;
|
||||
isJsonValid: boolean;
|
||||
isTypesValid: boolean;
|
||||
types: Record<string, any>;
|
||||
typesPlaceholder: string | null;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
onStatusChange: (status: ActionStatus) => void;
|
||||
}
|
||||
|
||||
function Developer ({ className = '', onStatusChange }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [code, setCode] = useState(EMPTY_CODE);
|
||||
const [isJsonValid, setIsJsonValid] = useState(true);
|
||||
const [isTypesValid, setIsTypesValid] = useState(true);
|
||||
const [types, setTypes] = useState<Record<string, any>>(EMPTY_TYPES);
|
||||
const [typesPlaceholder, setTypesPlaceholder] = useState<string | null>(null);
|
||||
const [sharedUrl, setSharedUrl] = useState<string | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
const types = decodeUrlTypes() || store.get('types') as Record<string, unknown> || {};
|
||||
|
||||
if (Object.keys(types).length) {
|
||||
setCode(JSON.stringify(types, null, 2));
|
||||
setTypes({});
|
||||
setTypesPlaceholder(Object.keys(types).join(', '));
|
||||
setSharedUrl(encodeUrlTypes(types));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const _setState = useCallback(
|
||||
({ code, isJsonValid, isTypesValid, types, typesPlaceholder }: AllState): void => {
|
||||
setCode(code);
|
||||
setIsJsonValid(isJsonValid);
|
||||
setIsTypesValid(isTypesValid);
|
||||
setTypes(types);
|
||||
setTypesPlaceholder(typesPlaceholder);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const _clearTypes = useCallback(
|
||||
(): void => {
|
||||
store.remove('types');
|
||||
|
||||
_setState({
|
||||
code: EMPTY_CODE,
|
||||
isJsonValid: true,
|
||||
isTypesValid: true,
|
||||
types: EMPTY_TYPES,
|
||||
typesPlaceholder: null
|
||||
});
|
||||
},
|
||||
[_setState]
|
||||
);
|
||||
|
||||
const _onChangeTypes = useCallback(
|
||||
(data: Uint8Array): void => {
|
||||
const code = u8aToString(data);
|
||||
|
||||
try {
|
||||
const types = JSON.parse(code) as Record<string, unknown>;
|
||||
const typesPlaceholder = Object.keys(types).join(', ');
|
||||
|
||||
console.log('Detected types:', typesPlaceholder);
|
||||
|
||||
_setState({
|
||||
code,
|
||||
isJsonValid: true,
|
||||
isTypesValid: true,
|
||||
types: Object.keys(types).length === 0 ? {} : types,
|
||||
typesPlaceholder
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error registering types:', error);
|
||||
|
||||
_setState({
|
||||
code,
|
||||
isJsonValid: false,
|
||||
isTypesValid: false,
|
||||
types: {},
|
||||
typesPlaceholder: (error as Error).message
|
||||
});
|
||||
}
|
||||
},
|
||||
[_setState]
|
||||
);
|
||||
|
||||
const _onEditTypes = useCallback(
|
||||
(code: string): void => {
|
||||
try {
|
||||
if (!isJsonObject(code)) {
|
||||
throw Error('This is not a valid JSON object.');
|
||||
}
|
||||
|
||||
_onChangeTypes(stringToU8a(code));
|
||||
} catch (error) {
|
||||
setCode(code);
|
||||
setIsJsonValid(false);
|
||||
setTypesPlaceholder((error as Error).message);
|
||||
}
|
||||
},
|
||||
[_onChangeTypes]
|
||||
);
|
||||
|
||||
const _saveDeveloper = useCallback(
|
||||
(): void => {
|
||||
let url = null;
|
||||
|
||||
try {
|
||||
api.registerTypes(types);
|
||||
store.set('types', types);
|
||||
setIsTypesValid(true);
|
||||
onStatusChange({
|
||||
action: t('Your custom types have been added'),
|
||||
status: 'success'
|
||||
});
|
||||
|
||||
if (Object.keys(types).length) {
|
||||
url = encodeUrlTypes(types);
|
||||
|
||||
console.log(url);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setIsTypesValid(false);
|
||||
onStatusChange({
|
||||
action: t(`Error saving your custom types. ${(error as Error).message}`),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
setSharedUrl(url);
|
||||
},
|
||||
[api, onStatusChange, t, types]
|
||||
);
|
||||
|
||||
const typesHasNoEntries = Object.keys(types).length === 0;
|
||||
|
||||
// Trans component
|
||||
/* eslint-disable react/jsx-max-props-per-line */
|
||||
|
||||
return (
|
||||
<StyledDiv className={className}>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<InputFile
|
||||
clearContent={typesHasNoEntries && isTypesValid}
|
||||
isError={!isTypesValid}
|
||||
label={t('Additional types as a JSON file (or edit below)')}
|
||||
onChange={_onChangeTypes}
|
||||
placeholder={typesPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Editor
|
||||
className='editor'
|
||||
code={code}
|
||||
isValid={isJsonValid}
|
||||
onEdit={_onEditTypes}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Trans i18nKey='devConfig'><div className='help'>If you are a development team with at least a test network available, consider adding the types directly <a href='https://github.com/pezkuwi-js/apps/tree/master/packages/apps-config' rel='noopener noreferrer' target='_blank'>to the apps-config</a>, allowing out of the box operation for your spec & chains, both for you and anybody trying to connect to it. This is not a replacement for your chain-specific UI, however doing so does help in allowing users to easily discover and use with zero-config.</div></Trans>
|
||||
</div>
|
||||
</div>
|
||||
<Button.Group>
|
||||
<CopyButton
|
||||
label={t('Share')}
|
||||
type={t('url')}
|
||||
value={sharedUrl}
|
||||
/>
|
||||
<Button
|
||||
icon='sync'
|
||||
label={t('Reset')}
|
||||
onClick={_clearTypes}
|
||||
/>
|
||||
<Button
|
||||
icon='save'
|
||||
isDisabled={!isTypesValid || !isJsonValid}
|
||||
label={t('Save')}
|
||||
onClick={_saveDeveloper}
|
||||
/>
|
||||
</Button.Group>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
.editor {
|
||||
height: 21rem;
|
||||
margin-left: 2rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.help {
|
||||
padding: 0.5rem 2rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Developer);
|
||||
Reference in New Issue
Block a user