mirror of
https://github.com/Qortal/qortal-mobile.git
synced 2025-05-10 11:47:52 +00:00
fix payment input
This commit is contained in:
parent
cdcdfa66fb
commit
e9dddfbf88
35
src/App.tsx
35
src/App.tsx
@ -126,6 +126,7 @@ import { handleGetFileFromIndexedDB } from "./utils/indexedDB";
|
|||||||
import { Wallets } from "./Wallets";
|
import { Wallets } from "./Wallets";
|
||||||
import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials";
|
import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials";
|
||||||
import { Tutorials } from "./components/Tutorials/Tutorials";
|
import { Tutorials } from "./components/Tutorials/Tutorials";
|
||||||
|
import BoundedNumericTextField from "./common/BoundedNumericTextField";
|
||||||
|
|
||||||
|
|
||||||
type extStates =
|
type extStates =
|
||||||
@ -745,7 +746,8 @@ function App() {
|
|||||||
setLtcBalanceLoading(false);
|
setLtcBalanceLoading(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const sendCoinFunc = () => {
|
const sendCoinFunc = async() => {
|
||||||
|
try {
|
||||||
setSendPaymentError("");
|
setSendPaymentError("");
|
||||||
setSendPaymentSuccess("");
|
setSendPaymentSuccess("");
|
||||||
if (!paymentTo) {
|
if (!paymentTo) {
|
||||||
@ -760,6 +762,12 @@ function App() {
|
|||||||
setSendPaymentError("Please enter your wallet password");
|
setSendPaymentError("Please enter your wallet password");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const fee = await getFee('PAYMENT')
|
||||||
|
|
||||||
|
await show({
|
||||||
|
message: `Would you like to transfer ${Number(paymentAmount)} QORT?` ,
|
||||||
|
paymentFee: fee.fee + ' QORT'
|
||||||
|
})
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
window
|
window
|
||||||
.sendMessage("sendCoin", {
|
.sendMessage("sendCoin", {
|
||||||
@ -780,6 +788,10 @@ function App() {
|
|||||||
console.error("Failed to send coin:", error);
|
console.error("Failed to send coin:", error);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
//error
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearAllStates = () => {
|
const clearAllStates = () => {
|
||||||
@ -1854,12 +1866,14 @@ console.log('openTutorialModal3', openTutorialModal)
|
|||||||
Amount
|
Amount
|
||||||
</CustomLabel>
|
</CustomLabel>
|
||||||
<Spacer height="5px" />
|
<Spacer height="5px" />
|
||||||
<CustomInput
|
<BoundedNumericTextField
|
||||||
id="standard-adornment-amount"
|
|
||||||
type="number"
|
|
||||||
value={paymentAmount}
|
value={paymentAmount}
|
||||||
onChange={(e) => setPaymentAmount(+e.target.value)}
|
minValue={0}
|
||||||
autoComplete="off"
|
maxValue={+balance}
|
||||||
|
allowDecimals={true}
|
||||||
|
initialValue={'0'}
|
||||||
|
allowNegatives={false}
|
||||||
|
afterChange={(e: string) => setPaymentAmount(+e)}
|
||||||
/>
|
/>
|
||||||
<Spacer height="6px" />
|
<Spacer height="6px" />
|
||||||
<CustomLabel htmlFor="standard-adornment-password">
|
<CustomLabel htmlFor="standard-adornment-password">
|
||||||
@ -2895,14 +2909,21 @@ await showInfo({
|
|||||||
aria-labelledby="alert-dialog-title"
|
aria-labelledby="alert-dialog-title"
|
||||||
aria-describedby="alert-dialog-description"
|
aria-describedby="alert-dialog-description"
|
||||||
>
|
>
|
||||||
<DialogTitle id="alert-dialog-title">{"Publish"}</DialogTitle>
|
<DialogTitle id="alert-dialog-title">{message.paymentFee ? "Payment" : "Publish"}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText id="alert-dialog-description">
|
<DialogContentText id="alert-dialog-description">
|
||||||
{message.message}
|
{message.message}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
{message?.paymentFee && (
|
||||||
|
<DialogContentText id="alert-dialog-description2">
|
||||||
|
payment fee: {message.paymentFee}
|
||||||
|
</DialogContentText>
|
||||||
|
)}
|
||||||
|
{message?.publishFee && (
|
||||||
<DialogContentText id="alert-dialog-description2">
|
<DialogContentText id="alert-dialog-description2">
|
||||||
publish fee: {message.publishFee}
|
publish fee: {message.publishFee}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button sx={{
|
<Button sx={{
|
||||||
|
@ -2154,7 +2154,7 @@ export async function sendCoin(
|
|||||||
|
|
||||||
const lastRef = await getLastRef();
|
const lastRef = await getLastRef();
|
||||||
const fee = await sendQortFee();
|
const fee = await sendQortFee();
|
||||||
const validApi = await findUsableApi();
|
const validApi = null;
|
||||||
|
|
||||||
const res = await makeTransactionRequest(
|
const res = await makeTransactionRequest(
|
||||||
confirmReceiver,
|
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 { useState } from 'react'
|
||||||
|
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||||
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
export const CustomInput = styled(TextField)({
|
export const CustomInput = styled(TextField)({
|
||||||
width: "183px", // Adjust the width as needed
|
width: "183px", // Adjust the width as needed
|
||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
@ -51,7 +52,11 @@ export const PasswordField: React.FunctionComponent<TextFieldProps> = ({ ...prop
|
|||||||
<InputAdornment position="end" data-testid="toggle-view-password-btn" onClick={() => {
|
<InputAdornment position="end" data-testid="toggle-view-password-btn" onClick={() => {
|
||||||
setCanViewPassword((prevState) => !prevState)
|
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>
|
</InputAdornment>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user