mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-11-10 01:07:05 +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:
commit
d21bfb1320
5867 files changed
+329019
No files matched your search
@@ -0,0 +1,69 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// augment package
|
||||
import '@pezkuwi/api-augment/bizinikiwi';
|
||||
|
||||
import type { PalletAssetsAssetAccount } from '@pezkuwi/types/lookup';
|
||||
import type { bool } from '@pezkuwi/types-codec';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { AddressSmall } from '@pezkuwi/react-components';
|
||||
import { FormatBalance } from '@pezkuwi/react-query';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Transfer from './Transfer.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore This looks correct in the editor, but incorrect in composite mode
|
||||
interface AccountExt extends PalletAssetsAssetAccount {
|
||||
isFrozen?: bool;
|
||||
sufficient?: bool
|
||||
}
|
||||
|
||||
interface Props {
|
||||
account: AccountExt;
|
||||
accountId: string;
|
||||
assetId: BN;
|
||||
className?: string;
|
||||
minBalance: BN;
|
||||
siFormat: [number, string];
|
||||
}
|
||||
|
||||
function Account ({ account: { balance, isFrozen, reason, sufficient }, accountId, assetId, minBalance, siFormat }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<td className='address'>
|
||||
<AddressSmall value={accountId} />
|
||||
</td>
|
||||
<td className='start'>
|
||||
{isFrozen?.isTrue ? t('Yes') : t('No')}
|
||||
</td>
|
||||
<td className='start'>
|
||||
{sufficient
|
||||
? sufficient.isTrue ? t('Yes') : t('No')
|
||||
: reason?.toString()}
|
||||
</td>
|
||||
<td className='number all'>
|
||||
<FormatBalance
|
||||
format={siFormat}
|
||||
value={balance}
|
||||
/>
|
||||
</td>
|
||||
<td className='button'>
|
||||
<Transfer
|
||||
accountId={accountId}
|
||||
assetId={assetId}
|
||||
minBalance={minBalance}
|
||||
siFormat={siFormat}
|
||||
/>
|
||||
</td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Account);
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AssetInfoComplete } from '@pezkuwi/react-hooks/types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { styled } from '@pezkuwi/react-components';
|
||||
import { formatNumber } from '@pezkuwi/util';
|
||||
|
||||
import Account from './Account.js';
|
||||
import useBalances from './useBalances.js';
|
||||
|
||||
interface Props {
|
||||
asset: AssetInfoComplete,
|
||||
className?: string;
|
||||
searchValue: string;
|
||||
}
|
||||
|
||||
const Asset = ({ asset: { details, id, metadata }, className, searchValue }: Props) => {
|
||||
const balances = useBalances(id);
|
||||
|
||||
const siFormat = useMemo(
|
||||
(): [number, string] => metadata
|
||||
? [metadata.decimals.toNumber(), metadata.symbol.toUtf8().toUpperCase()]
|
||||
: [0, 'NONE'],
|
||||
[metadata]
|
||||
);
|
||||
|
||||
const shouldShowAsset = useMemo(() => metadata.name.toUtf8().toLowerCase().includes(searchValue) ||
|
||||
formatNumber(id).toString().replaceAll(',', '').includes(searchValue), [id, metadata.name, searchValue]);
|
||||
|
||||
if (!balances?.length || !shouldShowAsset) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return balances.map(({ account, accountId }, index) => {
|
||||
return (
|
||||
<StyledTr
|
||||
className={`isExpanded ${className}`}
|
||||
isFirstItem={index === 0}
|
||||
isLastItem={index === balances.length - 1}
|
||||
key={accountId}
|
||||
>
|
||||
<td className='all'>
|
||||
{index === 0 && <>{metadata.name.toUtf8()} ({formatNumber(id)})</>}
|
||||
</td>
|
||||
<Account
|
||||
account={account}
|
||||
accountId={accountId}
|
||||
assetId={id}
|
||||
key={accountId}
|
||||
minBalance={details.minBalance}
|
||||
siFormat={siFormat}
|
||||
/>
|
||||
</StyledTr>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const BASE_BORDER = 0.125;
|
||||
const BORDER_TOP = `${BASE_BORDER * 3}rem solid var(--bg-page)`;
|
||||
const BORDER_RADIUS = `${BASE_BORDER * 4}rem`;
|
||||
|
||||
const StyledTr = styled.tr<{isFirstItem: boolean; isLastItem: boolean}>`
|
||||
td {
|
||||
border-top: ${(props) => props.isFirstItem && BORDER_TOP};
|
||||
border-radius: 0rem !important;
|
||||
|
||||
&:first-child {
|
||||
padding-block: 1rem !important;
|
||||
border-top-left-radius: ${(props) => props.isFirstItem ? BORDER_RADIUS : '0rem'}!important;
|
||||
border-bottom-left-radius: ${(props) => props.isLastItem ? BORDER_RADIUS : '0rem'}!important;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: ${(props) => props.isFirstItem ? BORDER_RADIUS : '0rem'}!important;
|
||||
border-bottom-right-radius: ${(props) => props.isLastItem ? BORDER_RADIUS : '0rem'}!important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Asset;
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Button, InputAddress, InputBalance, Modal, Toggle, TxButton } from '@pezkuwi/react-components';
|
||||
import { useApi, useToggle } from '@pezkuwi/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
accountId: string;
|
||||
assetId: BN;
|
||||
className?: string;
|
||||
minBalance: BN;
|
||||
siFormat: [number, string];
|
||||
}
|
||||
|
||||
function Transfer ({ accountId, assetId, className, minBalance, siFormat: [siDecimals, siSymbol] }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [isOpen, toggleOpen] = useToggle();
|
||||
const [amount, setAmount] = useState<BN | undefined>();
|
||||
const [recipientId, setRecipientId] = useState<string | null>(null);
|
||||
const [isProtected, setIsProtected] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon='paper-plane'
|
||||
label={t('send')}
|
||||
onClick={toggleOpen}
|
||||
/>
|
||||
{isOpen && (
|
||||
<Modal
|
||||
className={className}
|
||||
header={t('transfer asset')}
|
||||
onClose={toggleOpen}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns hint={t('The account to transfer from. This account should have sufficient assets for this transfer.')}>
|
||||
<InputAddress
|
||||
defaultValue={accountId}
|
||||
isDisabled
|
||||
label={t('send from')}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t('The beneficiary will have access to the transferred asset when the transaction is included in a block.')}>
|
||||
<InputAddress
|
||||
label={t('send to address')}
|
||||
onChange={setRecipientId}
|
||||
type='allPlus'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t('The amount of tokens to transfer to the account.')}>
|
||||
<InputBalance
|
||||
autoFocus
|
||||
label={t('amount to transfer')}
|
||||
onChange={setAmount}
|
||||
siDecimals={siDecimals}
|
||||
siSymbol={siSymbol}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t('The minimum balance allowed for the asset.')}>
|
||||
<InputBalance
|
||||
defaultValue={minBalance}
|
||||
isDisabled
|
||||
label={t('minimum balance')}
|
||||
siDecimals={siDecimals}
|
||||
siSymbol={siSymbol}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t('With the keep-alive option set, the account is protected against removal due to low balances.')}>
|
||||
<Toggle
|
||||
className='typeToggle'
|
||||
label={
|
||||
isProtected
|
||||
? t('Transfer with account keep-alive checks')
|
||||
: t('Normal transfer without keep-alive checks')
|
||||
}
|
||||
onChange={setIsProtected}
|
||||
value={isProtected}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='paper-plane'
|
||||
isDisabled={!recipientId || !amount}
|
||||
label={t('Send')}
|
||||
onStart={toggleOpen}
|
||||
params={[assetId, recipientId, amount]}
|
||||
tx={
|
||||
isProtected
|
||||
? api.tx.assets.transferKeepAlive
|
||||
: api.tx.assets.transfer}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Transfer);
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AssetInfo, AssetInfoComplete } from '@pezkuwi/react-hooks/types';
|
||||
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Input, styled, Table } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Asset from './Asset.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
infos?: AssetInfo[];
|
||||
}
|
||||
|
||||
function Balances ({ className, infos = [] }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [{ searchValue }, onApplySearch] = useState({ searchValue: '' });
|
||||
|
||||
const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
|
||||
[t('asset'), 'start'],
|
||||
[t('accounts'), 'start'],
|
||||
[t('frozen'), 'start'],
|
||||
[t('sufficient'), 'start'],
|
||||
[t('free balance'), 'start'],
|
||||
[]
|
||||
]);
|
||||
|
||||
const onChangeInput = useCallback((e: string) => {
|
||||
onApplySearch({ searchValue: e });
|
||||
}, []);
|
||||
|
||||
const completeAssets = useMemo(
|
||||
() => infos
|
||||
.filter((i): i is AssetInfoComplete => !!(i.details && i.metadata) && !i.details.supply.isZero())
|
||||
,
|
||||
[infos]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDiv className={className}>
|
||||
<Input
|
||||
aria-label={t('Search by asset id or name')}
|
||||
className='full isSmall'
|
||||
label={t('Search')}
|
||||
onChange={onChangeInput}
|
||||
placeholder={t('Search by asset id or name')}
|
||||
value={searchValue}
|
||||
/>
|
||||
<Table
|
||||
empty={t('No accounts with balances found for the asset')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{completeAssets.map((asset) => {
|
||||
return (
|
||||
<Asset
|
||||
asset={asset}
|
||||
key={asset.id.toString()}
|
||||
searchValue={searchValue.toLowerCase()}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Table>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
input {
|
||||
max-width: 250px !important;
|
||||
}
|
||||
|
||||
table {
|
||||
overflow: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Balances);
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletAssetsAssetAccount } from '@pezkuwi/types/lookup';
|
||||
import type { Option } from '@pezkuwi/types-codec';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useAccounts, useApi, useCall } from '@pezkuwi/react-hooks';
|
||||
|
||||
interface AccountResult {
|
||||
accountId: string;
|
||||
account: PalletAssetsAssetAccount;
|
||||
}
|
||||
|
||||
interface Result {
|
||||
assetId: BN;
|
||||
accounts: AccountResult[];
|
||||
}
|
||||
|
||||
function isOptional (value: PalletAssetsAssetAccount | Option<PalletAssetsAssetAccount>): value is Option<PalletAssetsAssetAccount> {
|
||||
return (value as Option<PalletAssetsAssetAccount>).isSome || (value as Option<PalletAssetsAssetAccount>).isNone;
|
||||
}
|
||||
|
||||
const OPTS = {
|
||||
transform: ([[params], accounts]: [[[BN, string][]], (PalletAssetsAssetAccount | Option<PalletAssetsAssetAccount>)[]]): Result => ({
|
||||
accounts: params
|
||||
.map(([, accountId], index) => {
|
||||
const o = accounts[index];
|
||||
|
||||
return {
|
||||
account: isOptional(o)
|
||||
? o.unwrapOr(null)
|
||||
: o,
|
||||
accountId
|
||||
};
|
||||
})
|
||||
.filter((a): a is AccountResult =>
|
||||
!!a.account &&
|
||||
!a.account.balance.isZero()
|
||||
),
|
||||
assetId: params[0][0]
|
||||
}),
|
||||
withParamsTransform: true
|
||||
};
|
||||
|
||||
function useBalancesImpl (id?: BN | null): AccountResult[] | null {
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const keys = useMemo(
|
||||
() => [allAccounts.map((a) => [id, a]).filter((tup) => !!tup[0])],
|
||||
[allAccounts, id]
|
||||
);
|
||||
const query = useCall(keys && api.query.assets.account.multi, keys, OPTS);
|
||||
|
||||
return (query && id && (query.assetId === id) && query.accounts) || null;
|
||||
}
|
||||
|
||||
export default createNamedHook('useBalances', useBalancesImpl);
|
||||
Reference in new issue
Block a user