Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bridge back to stellar - max amount chosen by the user allow for the amount that is incompatible #506

Merged
49 changes: 34 additions & 15 deletions src/components/Form/From/AvailableActions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
import { trimMaxDecimals, USER_INPUT_MAX_DECIMALS } from '../../../../shared/parseNumbers/maxDecimals';

interface AvailableActionsProps {
max?: number;
maxDecimals?: number;
setValue?: (n: number) => void;
}

export const AvailableActions = ({ max, setValue }: AvailableActionsProps) => (
<div className="flex gap-1 text-sm">
{max !== undefined && setValue !== undefined && (
<>
<span className="mr-1">Available: {max.toFixed(2)}</span>
<button className="text-primary hover:underline" onClick={() => setValue(Number(max) * 0.5)} type="button">
50%
</button>
<button className="text-primary hover:underline" onClick={() => setValue(Number(max))} type="button">
MAX
</button>
</>
)}
</div>
);
export const AvailableActions = ({
max,
maxDecimals = USER_INPUT_MAX_DECIMALS.PENDULUM,
setValue,
}: AvailableActionsProps) => {
const handleSetValue = (percentage: number) => {
if (max !== undefined && setValue !== undefined) {
const trimmedValue = trimMaxDecimals(String(max * percentage), maxDecimals);
setValue(Number(trimmedValue));
}
};

const handleSetHalf = () => handleSetValue(0.5);
const handleSetMax = () => handleSetValue(1);

return (
<div className="flex gap-1 text-sm">
{max !== undefined && setValue !== undefined && (
<>
<span className="mr-1">Available: {max.toFixed(2)}</span>
<button className="text-primary hover:underline" onClick={handleSetHalf} type="button">
50%
</button>
<button className="text-primary hover:underline" onClick={handleSetMax} type="button">
MAX
</button>
</>
)}
</div>
);
};
27 changes: 3 additions & 24 deletions src/components/Form/From/NumericInput/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Input } from 'react-daisyui';
import { UseFormRegisterReturn } from 'react-hook-form';
import { USER_INPUT_MAX_DECIMALS, exceedsMaxDecimals } from '../../../../shared/parseNumbers/decimal';
import { handleOnKeyPressExceedsMaxDecimals, USER_INPUT_MAX_DECIMALS } from '../../../../shared/parseNumbers/maxDecimals';

interface NumericInputProps {
register: UseFormRegisterReturn;
Expand All @@ -11,32 +11,11 @@ interface NumericInputProps {
autoFocus?: boolean;
}

function isValidNumericInput(value: string): boolean {
return /^[0-9.,]*$/.test(value);
}

function alreadyHasDecimal(e: KeyboardEvent) {
const decimalChars = ['.', ','];

// In the onInput event, "," is replaced by ".", so we check if the e.target.value already contains a "."
return decimalChars.some((char) => e.key === char && e.target && (e.target as HTMLInputElement).value.includes('.'));
}

function handleOnInput(e: KeyboardEvent): void {
const target = e.target as HTMLInputElement;
target.value = target.value.replace(/,/g, '.');
}

function handleOnKeyPress(e: KeyboardEvent, maxDecimals: number): void {
if (!isValidNumericInput(e.key) || alreadyHasDecimal(e)) {
e.preventDefault();
}
const target = e.target as HTMLInputElement;
if (exceedsMaxDecimals(target.value, maxDecimals - 1)) {
target.value = target.value.slice(0, -1);
}
}

export const NumericInput = ({
register,
readOnly = false,
Expand All @@ -45,7 +24,7 @@ export const NumericInput = ({
defaultValue,
autoFocus,
}: NumericInputProps) => (
<div className="w-full flex justify-between">
<div className="flex justify-between w-full">
<div className="flex-grow text-4xl text-black font-outfit">
<Input
autocomplete="off"
Expand All @@ -56,7 +35,7 @@ export const NumericInput = ({
additionalStyle
}
minlength="1"
onKeyPress={(e: KeyboardEvent) => handleOnKeyPress(e, maxDecimals)}
onKeyPress={(e: KeyboardEvent) => handleOnKeyPressExceedsMaxDecimals(e, maxDecimals)}
onInput={handleOnInput}
pattern="^[0-9]*[.,]?[0-9]*$"
placeholder="0.0"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Form/From/variants/StandardFrom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const StandardFrom = ({
</div>
<div className="flex justify-between items-center mt-1 dark:text-neutral-400 text-neutral-500">
<FromDescription network={network} customText={customText} />
<AvailableActions max={max} setValue={setValue} />
<AvailableActions max={max} setValue={setValue} maxDecimals={maxDecimals} />
</div>
</div>
<label className="label">{error && <span className="label-text text-red-400">{error}</span>}</label>
Expand Down
6 changes: 3 additions & 3 deletions src/components/LabelledInputField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const LabelledInputField = forwardRef((props: Props & InputProps) => {

return (
<>
<div className="flex w-full component-preview items-center justify-center gap-2" style={style}>
<div className="form-control w-full">
<div className="flex items-center justify-center w-full gap-2 component-preview" style={style}>
<div className="w-full form-control">
<label className="label">
{label && <span className="label-text">{label}</span>}
{secondaryLabel && <span className="label-text-alt">{secondaryLabel}</span>}
Expand Down Expand Up @@ -57,7 +57,7 @@ const LabelledInputField = forwardRef((props: Props & InputProps) => {
</div>
</div>
</div>
<label className="label">{error && <span className="label-text text-red-400">{error}</span>}</label>
<label className="label">{error && <span className="text-red-400 label-text">{error}</span>}</label>
</>
);
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/nabla/common/AmountSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { fractionOfValue } from '../../../shared/parseNumbers/metric';
import { ContractBalance } from '../../../helpers/contracts';
import { calcSharePercentageNumber } from '../../../helpers/calc';
import { NumericInput } from '../../Form/From/NumericInput';
import { USER_INPUT_MAX_DECIMALS } from '../../../shared/parseNumbers/decimal';
import { USER_INPUT_MAX_DECIMALS } from '../../../shared/parseNumbers/maxDecimals';

interface AmountSelectorProps<FormFieldValues extends FieldValues, TFieldName extends FieldPath<FormFieldValues>> {
maxBalance: ContractBalance | undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/pages/spacewalk/bridge/Issue/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import Disclaimer from './Disclaimer';
import { getIssueValidationSchema } from './IssueValidationSchema';
import { PAGES_PATHS } from '../../../../app';
import { isU128Compatible } from '../../../../shared/parseNumbers/isU128Compatible';
import { USER_INPUT_MAX_DECIMALS } from '../../../../shared/parseNumbers/decimal';
import { USER_INPUT_MAX_DECIMALS } from '../../../../shared/parseNumbers/maxDecimals';

interface IssueProps {
network: string;
Expand Down
20 changes: 10 additions & 10 deletions src/pages/spacewalk/bridge/Redeem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { yupResolver } from '@hookform/resolvers/yup';
import Big from 'big.js';
import { yupResolver } from '@hookform/resolvers/yup';
import { useEffect } from 'preact/compat';
import { useCallback, useMemo, useState } from 'preact/hooks';
import { Button } from 'react-daisyui';
import { useForm } from 'react-hook-form';
import { useGlobalState } from '../../../../GlobalStateProvider';
import { useNodeInfoState } from '../../../../NodeInfoProvider';
import From from '../../../../components/Form/From';
import LabelledInputField from '../../../../components/LabelledInputField';
import OpenWallet from '../../../../components/Wallet';
import { useGlobalState } from '../../../../GlobalStateProvider';
import { assetDisplayName } from '../../../../helpers/spacewalk';
import { isPublicKey } from '../../../../helpers/stellar';
import { getErrors, getEventBySectionAndMethod } from '../../../../helpers/substrate';
import { RichRedeemRequest, useRedeemPallet } from '../../../../hooks/spacewalk/useRedeemPallet';
import useBridgeSettings from '../../../../hooks/spacewalk/useBridgeSettings';
import useBalances from '../../../../hooks/useBalances';
import { useNodeInfoState } from '../../../../NodeInfoProvider';
import { decimalToStellarNative, nativeToDecimal } from '../../../../shared/parseNumbers/metric';
import { USER_INPUT_MAX_DECIMALS } from '../../../../shared/parseNumbers/maxDecimals';
import { ToastMessage, showToast } from '../../../../shared/showToast';
import { FeeBox } from '../FeeBox';
import { prioritizeXLMAsset } from '../helpers';
import { ConfirmationDialog } from './ConfirmationDialog';
import { getRedeemValidationSchema } from './RedeemValidationSchema';
import { ToastMessage, showToast } from '../../../../shared/showToast';
import { prioritizeXLMAsset } from '../helpers';
import { USER_INPUT_MAX_DECIMALS } from '../../../../shared/parseNumbers/decimal';

export type RedeemFormValues = {
amount: number;
Expand All @@ -43,7 +43,7 @@ function Redeem(props: RedeemProps): JSX.Element {
const { createRedeemRequestExtrinsic, getRedeemRequest } = useRedeemPallet();
const { selectedVault, selectedAsset, wrappedAssets, setSelectedAsset } = useBridgeSettings();

const { walletAccount, dAppName } = useGlobalState();
const { walletAccount } = useGlobalState();
const { api } = useNodeInfoState().state;
const { balances } = useBalances();
const { wrappedCurrencySuffix, nativeCurrency, network } = props;
Expand Down Expand Up @@ -132,14 +132,14 @@ function Redeem(props: RedeemProps): JSX.Element {
}, [api, getRedeemRequest, requestRedeemExtrinsic, selectedVault, walletAccount]);

return (
<div className="flex items-center justify-center h-full space-walk py-4 w-full">
<div className="flex items-center justify-center w-full h-full py-4 space-walk">
<ConfirmationDialog
redeemRequest={submittedRedeemRequest}
visible={confirmationDialogVisible}
onClose={() => setConfirmationDialogVisible(false)}
/>
<div className="w-full">
<form className="px-5 flex flex-col" onSubmit={handleSubmit(submitRequestRedeemExtrinsic)}>
<form className="flex flex-col px-5" onSubmit={handleSubmit(submitRequestRedeemExtrinsic)}>
<From
{...{
formControl: {
Expand All @@ -161,7 +161,7 @@ function Redeem(props: RedeemProps): JSX.Element {
badges: {},
}}
/>
<label className="label flex align-center">
<label className="flex label align-center">
<span className="text-sm">{`Max redeemable: ${maxRedeemable.toFixed(2)}
${selectedAsset?.code || ''}`}</span>
</label>
Expand Down
11 changes: 0 additions & 11 deletions src/shared/parseNumbers/decimal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ const MINIMUM_DECIMAL_PLACES = 2;
const DECIMAL_PLACES = 6;
const MICRO = 1e-6;

export enum USER_INPUT_MAX_DECIMALS {
PENDULUM = 12,
STELLAR = 7,
}

const formatNumberLessThanMicro = (tokenSymbol?: string) => {
return `< 0.000001 ${tokenSymbol ? tokenSymbol : ''}`;
};
Expand Down Expand Up @@ -40,9 +35,3 @@ export const nativeToFormatDecimal = (value: BigNumber | number | string, tokenS

// Without the tokenSymbol and prettyNumbers, formatNumberLessThanMicro functions
export const nativeToFormatDecimalPure = (value: BigNumber | number | string) => nativeToDecimal(value).toNumber();

export function exceedsMaxDecimals(value: unknown, maxDecimals: number) {
if (value === undefined || value === null) return true;
const decimalPlaces = value.toString().split('.')[1];
return decimalPlaces ? decimalPlaces.length > maxDecimals : false;
}
41 changes: 41 additions & 0 deletions src/shared/parseNumbers/maxDecimals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export enum USER_INPUT_MAX_DECIMALS {
PENDULUM = 12,
STELLAR = 7,
}

export function exceedsMaxDecimals(value: unknown, maxDecimals: number) {
if (value === undefined || value === null) return true;
const decimalPlaces = value.toString().split('.')[1];
return decimalPlaces ? decimalPlaces.length > maxDecimals : false;
}

export function trimMaxDecimals(value: string, maxDecimals: number): string {
const [integer, decimal] = value.split('.');
return decimal ? `${integer}.${decimal.slice(0, maxDecimals)}` : value;
}

function isValidNumericInput(value: string): boolean {
return /^[0-9.,]*$/.test(value);
}

function alreadyHasDecimal(e: KeyboardEvent) {
const decimalChars = ['.', ','];

// In the onInput event, "," is replaced by ".", so we check if the e.target.value already contains a "."
return decimalChars.some((char) => e.key === char && e.target && (e.target as HTMLInputElement).value.includes('.'));
}

function truncateIfExceedsMaxDecimals(value: string, maxDecimals: number): string {
if (exceedsMaxDecimals(value, maxDecimals - 1)) {
return value.slice(0, -1);
Sharqiewicz marked this conversation as resolved.
Show resolved Hide resolved
}
return value;
}

export function handleOnKeyPressExceedsMaxDecimals(e: KeyboardEvent, maxDecimals: number): void {
if (!isValidNumericInput(e.key) || alreadyHasDecimal(e)) {
e.preventDefault();
}
const target = e.target as HTMLInputElement;
target.value = truncateIfExceedsMaxDecimals(target.value, maxDecimals);
}
Loading