mirror of
https://github.com/Qortal/qortal-mobile.git
synced 2025-03-14 20:02:33 +00:00
fix payment input
This commit is contained in:
parent
cdcdfa66fb
commit
e9dddfbf88
107
src/App.tsx
107
src/App.tsx
@ -126,6 +126,7 @@ import { handleGetFileFromIndexedDB } from "./utils/indexedDB";
|
||||
import { Wallets } from "./Wallets";
|
||||
import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials";
|
||||
import { Tutorials } from "./components/Tutorials/Tutorials";
|
||||
import BoundedNumericTextField from "./common/BoundedNumericTextField";
|
||||
|
||||
|
||||
type extStates =
|
||||
@ -745,41 +746,52 @@ function App() {
|
||||
setLtcBalanceLoading(false);
|
||||
});
|
||||
};
|
||||
const sendCoinFunc = () => {
|
||||
setSendPaymentError("");
|
||||
setSendPaymentSuccess("");
|
||||
if (!paymentTo) {
|
||||
setSendPaymentError("Please enter a recipient");
|
||||
return;
|
||||
}
|
||||
if (!paymentAmount) {
|
||||
setSendPaymentError("Please enter an amount greater than 0");
|
||||
return;
|
||||
}
|
||||
if (!paymentPassword) {
|
||||
setSendPaymentError("Please enter your wallet password");
|
||||
return;
|
||||
}
|
||||
setIsLoading(true);
|
||||
window
|
||||
.sendMessage("sendCoin", {
|
||||
amount: Number(paymentAmount),
|
||||
receiver: paymentTo.trim(),
|
||||
password: paymentPassword,
|
||||
const sendCoinFunc = async() => {
|
||||
try {
|
||||
setSendPaymentError("");
|
||||
setSendPaymentSuccess("");
|
||||
if (!paymentTo) {
|
||||
setSendPaymentError("Please enter a recipient");
|
||||
return;
|
||||
}
|
||||
if (!paymentAmount) {
|
||||
setSendPaymentError("Please enter an amount greater than 0");
|
||||
return;
|
||||
}
|
||||
if (!paymentPassword) {
|
||||
setSendPaymentError("Please enter your wallet password");
|
||||
return;
|
||||
}
|
||||
const fee = await getFee('PAYMENT')
|
||||
|
||||
await show({
|
||||
message: `Would you like to transfer ${Number(paymentAmount)} QORT?` ,
|
||||
paymentFee: fee.fee + ' QORT'
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.error) {
|
||||
setSendPaymentError(response.error);
|
||||
} else {
|
||||
setIsOpenSendQort(false);
|
||||
setIsOpenSendQortSuccess(true);
|
||||
}
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to send coin:", error);
|
||||
setIsLoading(false);
|
||||
});
|
||||
setIsLoading(true);
|
||||
window
|
||||
.sendMessage("sendCoin", {
|
||||
amount: Number(paymentAmount),
|
||||
receiver: paymentTo.trim(),
|
||||
password: paymentPassword,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response?.error) {
|
||||
setSendPaymentError(response.error);
|
||||
} else {
|
||||
setIsOpenSendQort(false);
|
||||
setIsOpenSendQortSuccess(true);
|
||||
}
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to send coin:", error);
|
||||
setIsLoading(false);
|
||||
});
|
||||
} catch (error) {
|
||||
//error
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const clearAllStates = () => {
|
||||
@ -1854,12 +1866,14 @@ console.log('openTutorialModal3', openTutorialModal)
|
||||
Amount
|
||||
</CustomLabel>
|
||||
<Spacer height="5px" />
|
||||
<CustomInput
|
||||
id="standard-adornment-amount"
|
||||
type="number"
|
||||
<BoundedNumericTextField
|
||||
value={paymentAmount}
|
||||
onChange={(e) => setPaymentAmount(+e.target.value)}
|
||||
autoComplete="off"
|
||||
minValue={0}
|
||||
maxValue={+balance}
|
||||
allowDecimals={true}
|
||||
initialValue={'0'}
|
||||
allowNegatives={false}
|
||||
afterChange={(e: string) => setPaymentAmount(+e)}
|
||||
/>
|
||||
<Spacer height="6px" />
|
||||
<CustomLabel htmlFor="standard-adornment-password">
|
||||
@ -2895,14 +2909,21 @@ await showInfo({
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">{"Publish"}</DialogTitle>
|
||||
<DialogTitle id="alert-dialog-title">{message.paymentFee ? "Payment" : "Publish"}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
{message.message}
|
||||
</DialogContentText>
|
||||
<DialogContentText id="alert-dialog-description2">
|
||||
publish fee: {message.publishFee}
|
||||
</DialogContentText>
|
||||
{message?.paymentFee && (
|
||||
<DialogContentText id="alert-dialog-description2">
|
||||
payment fee: {message.paymentFee}
|
||||
</DialogContentText>
|
||||
)}
|
||||
{message?.publishFee && (
|
||||
<DialogContentText id="alert-dialog-description2">
|
||||
publish fee: {message.publishFee}
|
||||
</DialogContentText>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button sx={{
|
||||
|
@ -2154,7 +2154,7 @@ export async function sendCoin(
|
||||
|
||||
const lastRef = await getLastRef();
|
||||
const fee = await sendQortFee();
|
||||
const validApi = await findUsableApi();
|
||||
const validApi = null;
|
||||
|
||||
const res = await makeTransactionRequest(
|
||||
confirmReceiver,
|
||||
|
159
src/common/BoundedNumericTextField.tsx
Normal file
159
src/common/BoundedNumericTextField.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import {
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
TextField,
|
||||
TextFieldProps,
|
||||
} from "@mui/material";
|
||||
import React, { useRef, useState } from "react";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import {
|
||||
removeTrailingZeros,
|
||||
setNumberWithinBounds,
|
||||
} from "./numberFunctions.ts";
|
||||
import { CustomInput } from "../App-styles.ts";
|
||||
|
||||
type eventType = React.ChangeEvent<HTMLInputElement>;
|
||||
type BoundedNumericTextFieldProps = {
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
addIconButtons?: boolean;
|
||||
allowDecimals?: boolean;
|
||||
allowNegatives?: boolean;
|
||||
afterChange?: (s: string) => void;
|
||||
initialValue?: string;
|
||||
maxSigDigits?: number;
|
||||
} & TextFieldProps;
|
||||
|
||||
export const BoundedNumericTextField = ({
|
||||
minValue,
|
||||
maxValue,
|
||||
addIconButtons = true,
|
||||
allowDecimals = true,
|
||||
allowNegatives = false,
|
||||
afterChange,
|
||||
initialValue,
|
||||
maxSigDigits = 6,
|
||||
...props
|
||||
}: BoundedNumericTextFieldProps) => {
|
||||
const [textFieldValue, setTextFieldValue] = useState<string>(
|
||||
initialValue || ""
|
||||
);
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const stringIsEmpty = (value: string) => {
|
||||
return value === "";
|
||||
};
|
||||
const isAllZerosNum = /^0*\.?0*$/;
|
||||
const isFloatNum = /^-?[0-9]*\.?[0-9]*$/;
|
||||
const isIntegerNum = /^-?[0-9]+$/;
|
||||
const skipMinMaxCheck = (value: string) => {
|
||||
const lastIndexIsDecimal = value.charAt(value.length - 1) === ".";
|
||||
const isEmpty = stringIsEmpty(value);
|
||||
const isAllZeros = isAllZerosNum.test(value);
|
||||
const isInteger = isIntegerNum.test(value);
|
||||
// skipping minMax on all 0s allows values less than 1 to be entered
|
||||
|
||||
return lastIndexIsDecimal || isEmpty || (isAllZeros && !isInteger);
|
||||
};
|
||||
|
||||
const setMinMaxValue = (value: string): string => {
|
||||
if (skipMinMaxCheck(value)) return value;
|
||||
const valueNum = Number(value);
|
||||
|
||||
const boundedNum = setNumberWithinBounds(valueNum, minValue, maxValue);
|
||||
|
||||
const numberInBounds = boundedNum === valueNum;
|
||||
return numberInBounds ? value : boundedNum.toString();
|
||||
};
|
||||
|
||||
const getSigDigits = (number: string) => {
|
||||
if (isIntegerNum.test(number)) return 0;
|
||||
const decimalSplit = number.split(".");
|
||||
return decimalSplit[decimalSplit.length - 1].length;
|
||||
};
|
||||
|
||||
const sigDigitsExceeded = (number: string, sigDigits: number) => {
|
||||
return getSigDigits(number) > sigDigits;
|
||||
};
|
||||
|
||||
const filterTypes = (value: string) => {
|
||||
if (allowDecimals === false) value = value.replace(".", "");
|
||||
if (allowNegatives === false) value = value.replace("-", "");
|
||||
if (sigDigitsExceeded(value, maxSigDigits)) {
|
||||
value = value.substring(0, value.length - 1);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const filterValue = (value: string) => {
|
||||
if (stringIsEmpty(value)) return "";
|
||||
value = filterTypes(value);
|
||||
if (isFloatNum.test(value)) {
|
||||
return setMinMaxValue(value);
|
||||
}
|
||||
return textFieldValue;
|
||||
};
|
||||
|
||||
const listeners = (e: eventType) => {
|
||||
// console.log("changeEvent:", e);
|
||||
const newValue = filterValue(e.target.value);
|
||||
setTextFieldValue(newValue);
|
||||
if (afterChange) afterChange(newValue);
|
||||
};
|
||||
|
||||
const changeValueWithIncDecButton = (changeAmount: number) => {
|
||||
const changedValue = (+textFieldValue + changeAmount).toString();
|
||||
const inBoundsValue = setMinMaxValue(changedValue);
|
||||
setTextFieldValue(inBoundsValue);
|
||||
if (afterChange) afterChange(inBoundsValue);
|
||||
};
|
||||
|
||||
const formatValueOnBlur = (e: eventType) => {
|
||||
let value = e.target.value;
|
||||
if (stringIsEmpty(value) || value === ".") {
|
||||
setTextFieldValue("");
|
||||
return;
|
||||
}
|
||||
|
||||
value = setMinMaxValue(value);
|
||||
value = removeTrailingZeros(value);
|
||||
if (isAllZerosNum.test(value)) value = minValue.toString();
|
||||
|
||||
setTextFieldValue(value);
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { onChange, ...noChangeProps } = { ...props };
|
||||
return (
|
||||
<CustomInput
|
||||
{...noChangeProps}
|
||||
InputProps={{
|
||||
...props?.InputProps,
|
||||
endAdornment: addIconButtons ? (
|
||||
<InputAdornment position="end">
|
||||
<IconButton size="small" onClick={() => changeValueWithIncDecButton(1)}>
|
||||
<AddIcon sx={{
|
||||
color: 'white'
|
||||
}} />{" "}
|
||||
</IconButton>
|
||||
<IconButton size="small" onClick={() => changeValueWithIncDecButton(-1)}>
|
||||
<RemoveIcon sx={{
|
||||
color: 'white'
|
||||
}} />{" "}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
}}
|
||||
onChange={e => listeners(e as eventType)}
|
||||
onBlur={e => {
|
||||
formatValueOnBlur(e as eventType);
|
||||
}}
|
||||
autoComplete="off"
|
||||
value={textFieldValue}
|
||||
inputRef={ref}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BoundedNumericTextField;
|
63
src/common/numberFunctions.ts
Normal file
63
src/common/numberFunctions.ts
Normal file
@ -0,0 +1,63 @@
|
||||
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
||||
return Number(value).toFixed(sigDigits);
|
||||
};
|
||||
|
||||
export const removeTrailingZeros = (s: string) => {
|
||||
return Number(s).toString();
|
||||
};
|
||||
|
||||
export const setNumberWithinBounds = (
|
||||
num: number,
|
||||
minValue: number,
|
||||
maxValue: number
|
||||
) => {
|
||||
if (num > maxValue) return maxValue;
|
||||
if (num < minValue) return minValue;
|
||||
return num;
|
||||
};
|
||||
|
||||
export const numberToInt = (num: number) => {
|
||||
return Math.floor(num);
|
||||
};
|
||||
|
||||
type ByteFormat = "Decimal" | "Binary";
|
||||
export function formatBytes(
|
||||
bytes: number,
|
||||
decimals = 2,
|
||||
format: ByteFormat = "Binary"
|
||||
) {
|
||||
if (bytes === 0) return "0 Bytes";
|
||||
|
||||
const k = format === "Binary" ? 1024 : 1000;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
||||
}
|
||||
|
||||
export function formatTime(seconds: number): string {
|
||||
seconds = Math.floor(seconds);
|
||||
const minutes: number | string = Math.floor(seconds / 60);
|
||||
let hours: number | string = Math.floor(minutes / 60);
|
||||
|
||||
let remainingSeconds: number | string = seconds % 60;
|
||||
let remainingMinutes: number | string = minutes % 60;
|
||||
|
||||
if (remainingSeconds < 10) {
|
||||
remainingSeconds = "0" + remainingSeconds;
|
||||
}
|
||||
|
||||
if (remainingMinutes < 10) {
|
||||
remainingMinutes = "0" + remainingMinutes;
|
||||
}
|
||||
|
||||
if (hours === 0) {
|
||||
hours = "";
|
||||
} else {
|
||||
hours = hours + ":";
|
||||
}
|
||||
|
||||
return hours + remainingMinutes + ":" + remainingSeconds;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { Button, InputAdornment, TextField, TextFieldProps, styled } from "@mui/material";
|
||||
import { Button, ButtonBase, InputAdornment, TextField, TextFieldProps, styled } from "@mui/material";
|
||||
import { useState } from 'react'
|
||||
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
export const CustomInput = styled(TextField)({
|
||||
width: "183px", // Adjust the width as needed
|
||||
borderRadius: "5px",
|
||||
@ -51,7 +52,11 @@ export const PasswordField: React.FunctionComponent<TextFieldProps> = ({ ...prop
|
||||
<InputAdornment position="end" data-testid="toggle-view-password-btn" onClick={() => {
|
||||
setCanViewPassword((prevState) => !prevState)
|
||||
}}>
|
||||
{canViewPassword ? <Button data-testid="plain-text-indicator" sx={{ minWidth: 0, p: 0 }}>👁️</Button> : <Button data-testid="password-text-indicator" sx={{ minWidth: 0, p: 0 }}>👁️🗨️</Button>}
|
||||
{canViewPassword ? <ButtonBase data-testid="plain-text-indicator" sx={{ minWidth: 0, p: 0 }}><VisibilityOffIcon sx={{
|
||||
color: 'white'
|
||||
}}/></ButtonBase> : <ButtonBase data-testid="password-text-indicator" sx={{ minWidth: 0, p: 0 }}><VisibilityIcon sx={{
|
||||
color: 'white'
|
||||
}} /></ButtonBase>}
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user