feat: add transfer alias page

This commit is contained in:
AzizbekFayziyev 2026-03-21 15:02:10 +05:00
parent 523aac75cb
commit 67b31f03fd
10 changed files with 367 additions and 9 deletions

View file

@ -0,0 +1,7 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.8">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.967 0.75H5.53223C2.59381 0.75 0.75 2.83122 0.75 5.77645V13.7238C0.75 16.669 2.58408 18.7502 5.53223 18.7502H13.967C16.9152 18.7502 18.7502 16.669 18.7502 13.7238V5.77645C18.7502 2.83122 16.9152 0.75 13.967 0.75Z" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.75008 5.77447L9.75008 13.7257" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.10335 9.43658L9.7501 5.77426L13.3969 9.43658" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 742 B

View file

@ -0,0 +1,116 @@
import { useEffect, useState } from 'react';
import { useFeeCheck } from '../../../hooks/useFeeCheck';
import { useInput } from '../../../hooks/useInput';
import { fetchBackground } from '../../../utils/utils';
import { GetAliasByAdderssParams, TransferAliasParams } from '../types';
const fee = 0.01;
export const useAliasTransfer = ({ alias }: { alias: string }) => {
const { notEnoughFee } = useFeeCheck(fee);
const [addressStroke, setAddressStroke] = useState<null | string>(null);
const commentInput = useInput('', { isEmpty: false, customValidation: true });
const addressInput = useInput('', { isEmpty: false, customError: !!addressStroke });
const [isChecking, setIsChecking] = useState(false);
const [isSubmitting, setSubmitting] = useState(false);
const [isValidAddress, setValidAddress] = useState(false);
const [debouncedAddress, setDebouncedAddress] = useState(addressInput.value);
const [transactionSuccess, setTransactionSuccess] = useState<null | boolean>(null);
const [errorMsg, setErrorMsg] = useState('');
useEffect(() => {
setValidAddress(false);
const timer = setTimeout(() => {
setDebouncedAddress(addressInput.value);
}, 500);
return () => clearTimeout(timer);
}, [addressInput.value]);
useEffect(() => {
if (!debouncedAddress) return;
const checkAddress = async () => {
setIsChecking(true);
try {
const data = await fetchBackground({
method: 'GET_ALIAS_BY_ADDRESS',
address: debouncedAddress,
} as GetAliasByAdderssParams);
if (data.status === 'NOT_FOUND') {
setValidAddress(true);
setAddressStroke(null);
} else if (data.status === 'OK') {
setValidAddress(false);
setAddressStroke('This wallet already has an alias');
} else {
setValidAddress(false);
setAddressStroke('No wallet exists for the provided address');
}
} catch (err) {
console.log(err);
} finally {
setIsChecking(false);
}
};
checkAddress();
}, [debouncedAddress]);
const onTransfer = async () => {
setSubmitting(true);
try {
const data = await fetchBackground({
method: 'UPDATE_ALIAS',
address: addressInput.value,
alias,
comment: commentInput.value,
} as TransferAliasParams);
if (data?.result?.tx_id) {
setTransactionSuccess(true);
} else {
setTransactionSuccess(false);
if (data?.error?.code === -7) {
setErrorMsg('Not enough balance');
} else {
const deamonMsg = data?.error?.message;
setErrorMsg(deamonMsg || 'Unknown error');
}
}
} catch (err) {
console.log(err);
} finally {
setSubmitting(false);
}
};
const isDisabled =
!isValidAddress ||
addressInput.isEmpty ||
!addressInput.inputValid ||
isChecking ||
isSubmitting ||
!addressInput.value ||
notEnoughFee ||
Boolean(addressStroke);
return {
onTransfer,
transactionSuccess,
errorMsg,
isDisabled,
addressStroke,
commentInput,
addressInput,
notEnoughFee,
fee,
};
};

View file

@ -0,0 +1,105 @@
import React, { useContext } from 'react';
import { goBack } from 'react-chrome-extension-router';
import styles from './styles.module.scss';
import RoutersNav from '../UI/RoutersNav/RoutersNav';
import MyInput, { inputDataProps } from '../UI/MyInput/MyInput';
import Button from '../UI/Button/Button';
import InfoTooltip from '../UI/InfoTooltip';
import { Store } from '../../store/store-reducer';
import successImg from '../../assets/images/success-round.png';
import errorImg from '../../assets/images/failed-round.png';
import { useAliasTransfer } from './hook/useAliasTransfer';
const AliasTransfer = () => {
const { state } = useContext(Store);
const walletAlias = state.wallet?.alias;
const {
onTransfer,
addressStroke,
commentInput,
addressInput,
fee,
notEnoughFee,
errorMsg,
transactionSuccess,
isDisabled,
} = useAliasTransfer({
alias: walletAlias,
});
if (transactionSuccess !== null) {
return (
<div className={styles.main}>
<div className={styles.main__transactionInfo}>
<img
className={styles.main__transactionInfo_img}
src={transactionSuccess ? successImg : errorImg}
alt="transaction"
/>
<p className={styles.main__transactionInfo_text}>
{transactionSuccess ? `Alias transfered!` : 'Transaction failed!'}
</p>
{!transactionSuccess && (
<p className={styles.main__transactionInfo_errorMsg}>{errorMsg}</p>
)}
</div>
<Button className={styles.main__action} onClick={goBack}>
OK
</Button>
</div>
);
}
return (
<main className={styles.main}>
<RoutersNav title="Transfer Alias" />
<div className={styles.main__content}>
<MyInput
disabled
noActiveBorder
placeholder="Alias"
label="Alias"
value={`@${state.wallet.alias}`}
/>
<MyInput
stroke={addressStroke}
placeholder="Please enter an address"
label="Transfer to"
inputData={addressInput as inputDataProps}
/>
<MyInput
maxLength={255}
placeholder="Enter your comment here"
label="Comment"
inputData={commentInput as inputDataProps}
noActiveBorder
/>
<div className={styles.main__bottom}>
<div className={styles.fee}>
<h5 className={styles.fee__label}>
Transfer fee <InfoTooltip title="Total network fee" />
</h5>
<p className={`${styles.fee__value} ${notEnoughFee ? styles.error : ''}`}>
{fee} ZANO
</p>
</div>
<Button onClick={onTransfer} disabled={isDisabled}>
Transfer alias
</Button>
</div>
</div>
</main>
);
};
export default AliasTransfer;

View file

@ -0,0 +1,81 @@
@import 'src/app/styles/variables';
.main {
width: 100%;
height: calc($appHeight - 72px);
display: flex;
flex-direction: column;
&__content {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
}
&__transactionInfo {
margin-block: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
&_img {
width: 100px;
height: 100px;
}
&_text {
text-align: center;
font-size: 20px;
line-height: 100%;
color: #1f8feb;
}
&_errorMsg {
font-size: 16px;
text-align: center;
line-height: 140%;
width: 100%;
word-break: break-all;
}
}
&__action {
margin-top: auto;
margin-bottom: 24px;
}
&__bottom {
padding-bottom: 24px;
margin-top: auto;
display: flex;
flex-direction: column;
gap: 8px;
.fee {
z-index: 10;
display: flex;
justify-content: space-between;
&__label {
display: flex;
align-items: center;
gap: 8px;
font-size: 16px;
font-weight: 400;
color: #b6b6c4;
}
&__value {
font-size: 16px;
font-weight: 400;
&.error {
color: #ff6767;
}
}
}
}
}

View file

@ -0,0 +1,11 @@
export interface GetAliasByAdderssParams {
method: string;
address: string;
}
export interface TransferAliasParams {
method: string;
address: string;
alias: string;
comment?: string;
}

View file

@ -125,6 +125,11 @@
display: flex;
align-items: center;
gap: 8px;
&__actions {
display: flex;
align-items: center;
}
}
.aliasContent {

View file

@ -1,9 +1,11 @@
import React, { Dispatch, SetStateAction, useContext, useRef, useState } from 'react';
import Decimal from 'decimal.js';
import { styleText } from 'util';
import copyIcon from '../../assets/svg/copy.svg';
import dotsIcon from '../../assets/svg/dots.svg';
import sendIcon from '../../assets/svg/send.svg';
import editIcon from '../../assets/svg/edit.svg';
import transferIcon from '../../assets/svg/transfer.svg';
import settingsIcon from '../../assets/svg/settings.svg';
import showIcon from '../../assets/svg/show.svg';
import hideIcon from '../../assets/svg/hide.svg';
@ -21,6 +23,7 @@ import NavLink from '../UI/NavLink/NavLink';
import { classNames } from '../../utils/classNames';
import { ZANO_ASSET_ID } from '../../../constants';
import AliasManagePage from '../AliasManagePage';
import AliasTransfer from '../AliasTransfer';
const Wallet = ({ setConnectOpened }: { setConnectOpened: Dispatch<SetStateAction<boolean>> }) => {
const { state, dispatch } = useContext(Store);
@ -111,15 +114,28 @@ const Wallet = ({ setConnectOpened }: { setConnectOpened: Dispatch<SetStateActio
</div>
{state.wallet.alias && (
<NavLink
component={AliasManagePage}
props={{ mode: 'edit' }}
className="round-button"
>
<img width={18} height={18} src={editIcon} alt="edit icon" />
{/* Tooltip */}
<span>Edit alias</span>
</NavLink>
<div className={s.aliasWrapper__actions}>
<NavLink
component={AliasManagePage}
props={{ mode: 'edit' }}
className="round-button"
>
<img width={18} height={18} src={editIcon} alt="edit icon" />
{/* Tooltip */}
<span>Edit alias</span>
</NavLink>
<NavLink component={AliasTransfer} className="round-button">
<img
width={18}
height={18}
src={transferIcon}
alt="transfer icon"
/>
{/* Tooltip */}
<span>Transfer alias</span>
</NavLink>
</div>
)}
</div>
<div className={s.balanceWrapper}>

View file

@ -14,6 +14,7 @@ export async function fetchBackground(data: {
success?: boolean;
credentials?: { port: string };
alias?: string;
address?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}): Promise<any> {
return new Promise((resolve, reject) => {

View file

@ -19,6 +19,7 @@ import {
burnAsset,
getAsset,
updateAlias,
getAliasByAddress,
} from './wallet';
import { truncateToDecimals } from '../app/utils/utils';
@ -278,6 +279,7 @@ const SELF_ONLY_REQUESTS = [
'GET_TRANSFER_REQUEST',
'REGISTER_ALIAS',
'UPDATE_ALIAS',
'GET_ALIAS_BY_ADDRESS',
];
interface Sender {
id: string;
@ -590,6 +592,13 @@ async function processRequest(request: RequestType, sender: Sender, sendResponse
break;
}
case 'GET_ALIAS_BY_ADDRESS': {
getAliasByAddress(String(request.address))
.then((res) => sendResponse(res))
.catch(() => sendResponse({ error: 'Internal error' }));
break;
}
case 'IONIC_SWAP_ACCEPT': {
try {
const swapProposalRsp = await getSwapProposalInfo(request.hex_raw_proposal);

View file

@ -332,6 +332,13 @@ export const updateAlias = async ({
return data;
};
export const getAliasByAddress = async (address: string) => {
const response = await fetchData('get_alias_by_address', address);
const data = await response.json();
return data?.result;
};
export const transfer = async (
assetId = ZANO_ASSET_ID,
destination: string | undefined,