Files
xrpl-hooks-ide/components/Transaction/ui.tsx
2022-07-19 17:36:09 +05:30

332 lines
9.5 KiB
TypeScript

import { FC, useCallback, useEffect, useMemo, useState } from "react";
import Container from "../Container";
import Flex from "../Flex";
import Input from "../Input";
import Select from "../Select";
import Text from "../Text";
import {
SelectOption,
TransactionState,
transactionsOptions,
TxFields,
getTxFields,
defaultTransactionType,
} from "../../state/transactions";
import { useSnapshot } from "valtio";
import state from "../../state";
import { streamState } from "../DebugStream";
import { Button } from "..";
import Textarea from "../Textarea";
interface UIProps {
setState: (
pTx?: Partial<TransactionState> | undefined
) => TransactionState | undefined;
state: TransactionState;
estimateFee?: (...arg: any) => Promise<string | undefined>;
}
export const TxUI: FC<UIProps> = ({
state: txState,
setState,
estimateFee,
}) => {
const { accounts } = useSnapshot(state);
const {
selectedAccount,
selectedDestAccount,
selectedTransaction,
txFields,
} = txState;
const accountOptions: SelectOption[] = accounts.map(acc => ({
label: acc.name,
value: acc.address,
}));
const destAccountOptions: SelectOption[] = accounts
.map(acc => ({
label: acc.name,
value: acc.address,
}))
.filter(acc => acc.value !== selectedAccount?.value);
const [feeLoading, setFeeLoading] = useState(false);
const resetFields = useCallback(
(tt: string) => {
const fields = getTxFields(tt);
if (fields.Destination !== undefined) {
setState({ selectedDestAccount: null });
fields.Destination = "";
} else {
fields.Destination = undefined;
}
return setState({ txFields: fields });
},
[setState]
);
const handleSetAccount = (acc: SelectOption) => {
setState({ selectedAccount: acc });
streamState.selectedAccount = acc;
};
const handleSetField = useCallback(
(field: keyof TxFields, value: string, opFields?: TxFields) => {
const fields = opFields || txFields;
const obj = fields[field];
setState({
txFields: {
...fields,
[field]: typeof obj === "object" ? { ...obj, $value: value } : value,
},
});
},
[setState, txFields]
);
const handleEstimateFee = useCallback(
async (state?: TransactionState, silent?: boolean) => {
setFeeLoading(true);
const fee = await estimateFee?.(state, { silent });
if (fee) handleSetField("Fee", fee, state?.txFields);
setFeeLoading(false);
},
[estimateFee, handleSetField]
);
const handleChangeTxType = useCallback(
(tt: SelectOption) => {
setState({ selectedTransaction: tt });
const newState = resetFields(tt.value);
handleEstimateFee(newState, true);
},
[handleEstimateFee, resetFields, setState]
);
const switchToJson = () => setState({ viewType: "json" });
// default tx
useEffect(() => {
if (selectedTransaction?.value) return;
if (defaultTransactionType) {
handleChangeTxType(defaultTransactionType);
}
}, [handleChangeTxType, selectedTransaction?.value]);
const fields = useMemo(
() => getTxFields(selectedTransaction?.value),
[selectedTransaction?.value]
);
const specialFields = ["TransactionType", "Account"];
if (fields.Destination !== undefined) {
specialFields.push("Destination");
}
const otherFields = Object.keys(txFields).filter(
k => !specialFields.includes(k)
) as [keyof TxFields];
return (
<Container
css={{
p: "$3 01",
fontSize: "$sm",
height: "calc(100% - 45px)",
}}
>
<Flex column fluid css={{ height: "100%", overflowY: "auto", pr: "$1" }}>
<Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
mt: "1px",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
Transaction type:{" "}
</Text>
<Select
instanceId="transactionsType"
placeholder="Select transaction type"
options={transactionsOptions}
hideSelectedOptions
css={{ width: "70%" }}
value={selectedTransaction}
onChange={(tt: any) => handleChangeTxType(tt)}
/>
</Flex>
<Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
Account:{" "}
</Text>
<Select
instanceId="from-account"
placeholder="Select your account"
css={{ width: "70%" }}
options={accountOptions}
value={selectedAccount}
onChange={(acc: any) => handleSetAccount(acc)} // TODO make react-select have correct types for acc
/>
</Flex>
{fields.Destination !== undefined && (
<Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
mb: "$3",
pr: "1px",
}}
>
<Text muted css={{ mr: "$3" }}>
Destination account:{" "}
</Text>
<Select
instanceId="to-account"
placeholder="Select the destination account"
css={{ width: "70%" }}
options={destAccountOptions}
value={selectedDestAccount}
isClearable
onChange={(acc: any) => setState({ selectedDestAccount: acc })}
/>
</Flex>
)}
{otherFields.map(field => {
let _value = txFields[field];
let value: string | undefined;
if (typeof _value === "object") {
if (_value.$type === "json" && typeof _value.$value === "object") {
value = JSON.stringify(_value.$value, null, 2);
} else {
value = _value.$value.toString();
}
} else {
value = _value?.toString();
}
const isXrp = typeof _value === "object" && _value.$type === "xrp";
const isJson = typeof _value === "object" && _value.$type === "json";
const isFee = field === "Fee";
let rows = isJson
? (value?.match(/\n/gm)?.length || 0) + 1
: undefined;
if (rows && rows > 5) rows = 5;
return (
<Flex column key={field} css={{ mb: "$2", pr: "1px" }}>
<Flex
row
fluid
css={{
justifyContent: "flex-end",
alignItems: "center",
position: "relative",
}}
>
<Text muted css={{ mr: "$3" }}>
{field + (isXrp ? " (XRP)" : "")}:{" "}
</Text>
{isJson ? (
<Textarea
rows={rows}
value={value}
spellCheck={false}
onChange={switchToJson}
css={{
width: "70%",
flex: "inherit",
resize: "vertical",
}}
/>
) : (
<Input
type={isFee ? "number" : "text"}
value={value}
onChange={e => {
if (isFee) {
const val = e.target.value
.replaceAll(".", "")
.replaceAll(",", "");
handleSetField(field, val);
} else {
handleSetField(field, e.target.value);
}
}}
onKeyPress={
isFee
? e => {
if (e.key === "." || e.key === ",") {
e.preventDefault();
}
}
: undefined
}
css={{
width: "70%",
flex: "inherit",
"-moz-appearance": "textfield",
"&::-webkit-outer-spin-button": {
"-webkit-appearance": "none",
margin: 0,
},
"&::-webkit-inner-spin-button ": {
"-webkit-appearance": "none",
margin: 0,
},
}}
/>
)}
{isFee && (
<Button
size="xs"
variant="primary"
outline
disabled={txState.txIsDisabled}
isDisabled={txState.txIsDisabled}
isLoading={feeLoading}
css={{
position: "absolute",
right: "$2",
fontSize: "$xs",
cursor: "pointer",
alignContent: "center",
display: "flex",
}}
onClick={() => handleEstimateFee()}
>
Suggest
</Button>
)}
</Flex>
</Flex>
);
})}
</Flex>
</Container>
);
};