// 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; typesPlaceholder: string | null; } interface Props { className?: string; onStatusChange: (status: ActionStatus) => void; } function Developer ({ className = '', onStatusChange }: Props): React.ReactElement { 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>(EMPTY_TYPES); const [typesPlaceholder, setTypesPlaceholder] = useState(null); const [sharedUrl, setSharedUrl] = useState(null); useEffect((): void => { const types = decodeUrlTypes() || store.get('types') as Record || {}; 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; 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 (
If you are a development team with at least a test network available, consider adding the types directly to the apps-config, 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.