Readonly tx json view
This commit is contained in:
		@@ -1,377 +0,0 @@
 | 
			
		||||
import { Play } from "phosphor-react";
 | 
			
		||||
import { FC, useCallback, useEffect } from "react";
 | 
			
		||||
import { useSnapshot } from "valtio";
 | 
			
		||||
import transactionsData from "../content/transactions.json";
 | 
			
		||||
import state, { modifyTransaction } from "../state";
 | 
			
		||||
import { sendTransaction } from "../state/actions";
 | 
			
		||||
import Box from "./Box";
 | 
			
		||||
import Button from "./Button";
 | 
			
		||||
import Container from "./Container";
 | 
			
		||||
import { streamState } from "./DebugStream";
 | 
			
		||||
import Flex from "./Flex";
 | 
			
		||||
import Input from "./Input";
 | 
			
		||||
import Text from "./Text";
 | 
			
		||||
import Select from "./Select";
 | 
			
		||||
 | 
			
		||||
type TxFields = Omit<
 | 
			
		||||
  typeof transactionsData[0],
 | 
			
		||||
  "Account" | "Sequence" | "TransactionType"
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
type OtherFields = (keyof Omit<TxFields, "Destination">)[];
 | 
			
		||||
 | 
			
		||||
type SelectOption = {
 | 
			
		||||
  value: string;
 | 
			
		||||
  label: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface TransactionState {
 | 
			
		||||
  selectedTransaction: SelectOption | null;
 | 
			
		||||
  selectedAccount: SelectOption | null;
 | 
			
		||||
  selectedDestAccount: SelectOption | null;
 | 
			
		||||
  txIsLoading: boolean;
 | 
			
		||||
  txIsDisabled: boolean;
 | 
			
		||||
  txFields: TxFields;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TransactionProps {
 | 
			
		||||
  header: string;
 | 
			
		||||
  state: TransactionState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
  header,
 | 
			
		||||
  state: {
 | 
			
		||||
    selectedAccount,
 | 
			
		||||
    selectedDestAccount,
 | 
			
		||||
    selectedTransaction,
 | 
			
		||||
    txFields,
 | 
			
		||||
    txIsDisabled,
 | 
			
		||||
    txIsLoading,
 | 
			
		||||
  },
 | 
			
		||||
  ...props
 | 
			
		||||
}) => {
 | 
			
		||||
  const { accounts } = useSnapshot(state);
 | 
			
		||||
 | 
			
		||||
  const setState = useCallback(
 | 
			
		||||
    (pTx?: Partial<TransactionState>) => {
 | 
			
		||||
      modifyTransaction(header, pTx);
 | 
			
		||||
    },
 | 
			
		||||
    [header]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const transactionsOptions = transactionsData.map(tx => ({
 | 
			
		||||
    value: tx.TransactionType,
 | 
			
		||||
    label: tx.TransactionType,
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const transactionType = selectedTransaction?.value;
 | 
			
		||||
    const account = accounts.find(
 | 
			
		||||
      acc => acc.address === selectedAccount?.value
 | 
			
		||||
    );
 | 
			
		||||
    if (!account || !transactionType || txIsLoading) {
 | 
			
		||||
      setState({ txIsDisabled: true });
 | 
			
		||||
    } else {
 | 
			
		||||
      setState({ txIsDisabled: false });
 | 
			
		||||
    }
 | 
			
		||||
  }, [txIsLoading, selectedTransaction, selectedAccount, accounts, setState]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    let _txFields: TxFields | undefined = transactionsData.find(
 | 
			
		||||
      tx => tx.TransactionType === selectedTransaction?.value
 | 
			
		||||
    );
 | 
			
		||||
    if (!_txFields) return setState({ txFields: {} });
 | 
			
		||||
    _txFields = { ..._txFields } as TxFields;
 | 
			
		||||
 | 
			
		||||
    if (!_txFields.Destination) setState({ selectedDestAccount: null });
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    delete _txFields.TransactionType;
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    delete _txFields.Account;
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    delete _txFields.Sequence;
 | 
			
		||||
    setState({ txFields: _txFields });
 | 
			
		||||
  }, [setState, selectedTransaction]);
 | 
			
		||||
 | 
			
		||||
  const submitTest = useCallback(async () => {
 | 
			
		||||
    const account = accounts.find(
 | 
			
		||||
      acc => acc.address === selectedAccount?.value
 | 
			
		||||
    );
 | 
			
		||||
    const TransactionType = selectedTransaction?.value;
 | 
			
		||||
    if (!account || !TransactionType || txIsDisabled) return;
 | 
			
		||||
 | 
			
		||||
    setState({ txIsLoading: true });
 | 
			
		||||
    // setTxIsError(null)
 | 
			
		||||
    try {
 | 
			
		||||
      let options = { ...txFields };
 | 
			
		||||
 | 
			
		||||
      options.Destination = selectedDestAccount?.value;
 | 
			
		||||
      (Object.keys(options) as (keyof TxFields)[]).forEach(field => {
 | 
			
		||||
        let _value = options[field];
 | 
			
		||||
        // convert currency
 | 
			
		||||
        if (typeof _value === "object" && _value.type === "currency") {
 | 
			
		||||
          if (+_value.value) {
 | 
			
		||||
            options[field] = (+_value.value * 1000000 + "") as any;
 | 
			
		||||
          } else {
 | 
			
		||||
            options[field] = undefined; // 👇 💀
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        // handle type: `json`
 | 
			
		||||
        if (typeof _value === "object" && _value.type === "json") {
 | 
			
		||||
          if (typeof _value.value === "object") {
 | 
			
		||||
            options[field] = _value.value as any;
 | 
			
		||||
          } else {
 | 
			
		||||
            try {
 | 
			
		||||
              options[field] = JSON.parse(_value.value);
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
              const message = `Input error for json field '${field}': ${
 | 
			
		||||
                error instanceof Error ? error.message : ""
 | 
			
		||||
              }`;
 | 
			
		||||
              throw Error(message);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // delete unneccesary fields
 | 
			
		||||
        if (!options[field]) {
 | 
			
		||||
          delete options[field];
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
 | 
			
		||||
      await sendTransaction(
 | 
			
		||||
        account,
 | 
			
		||||
        {
 | 
			
		||||
          TransactionType,
 | 
			
		||||
          ...options,
 | 
			
		||||
        },
 | 
			
		||||
        { logPrefix }
 | 
			
		||||
      );
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(error);
 | 
			
		||||
      if (error instanceof Error) {
 | 
			
		||||
        state.transactionLogs.push({ type: "error", message: error.message });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    setState({ txIsLoading: false });
 | 
			
		||||
  }, [
 | 
			
		||||
    header,
 | 
			
		||||
    setState,
 | 
			
		||||
    selectedAccount?.value,
 | 
			
		||||
    selectedDestAccount?.value,
 | 
			
		||||
    selectedTransaction?.value,
 | 
			
		||||
    accounts,
 | 
			
		||||
    txFields,
 | 
			
		||||
    txIsDisabled,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const resetState = useCallback(() => {
 | 
			
		||||
    setState({});
 | 
			
		||||
  }, [setState]);
 | 
			
		||||
 | 
			
		||||
  const handleSetAccount = (acc: SelectOption) => {
 | 
			
		||||
    setState({ selectedAccount: acc });
 | 
			
		||||
    streamState.selectedAccount = acc;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const usualFields = ["TransactionType", "Amount", "Account", "Destination"];
 | 
			
		||||
  const otherFields = Object.keys(txFields).filter(
 | 
			
		||||
    k => !usualFields.includes(k)
 | 
			
		||||
  ) as OtherFields;
 | 
			
		||||
  return (
 | 
			
		||||
    <Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
 | 
			
		||||
      <Container
 | 
			
		||||
        css={{
 | 
			
		||||
          p: "$3 01",
 | 
			
		||||
          fontSize: "$sm",
 | 
			
		||||
          height: "calc(100% - 45px)",
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
 | 
			
		||||
          <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={(tx: any) => setState({ selectedTransaction: tx })}
 | 
			
		||||
            />
 | 
			
		||||
          </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>
 | 
			
		||||
          {txFields.Amount !== undefined && (
 | 
			
		||||
            <Flex
 | 
			
		||||
              row
 | 
			
		||||
              fluid
 | 
			
		||||
              css={{
 | 
			
		||||
                justifyContent: "flex-end",
 | 
			
		||||
                alignItems: "center",
 | 
			
		||||
                mb: "$3",
 | 
			
		||||
                pr: "1px",
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Text muted css={{ mr: "$3" }}>
 | 
			
		||||
                Amount (XRP):{" "}
 | 
			
		||||
              </Text>
 | 
			
		||||
              <Input
 | 
			
		||||
                value={txFields.Amount.value}
 | 
			
		||||
                onChange={e =>
 | 
			
		||||
                  setState({
 | 
			
		||||
                    txFields: {
 | 
			
		||||
                      ...txFields,
 | 
			
		||||
                      Amount: { type: "currency", value: e.target.value },
 | 
			
		||||
                    },
 | 
			
		||||
                  })
 | 
			
		||||
                }
 | 
			
		||||
                css={{ width: "70%", flex: "inherit" }}
 | 
			
		||||
              />
 | 
			
		||||
            </Flex>
 | 
			
		||||
          )}
 | 
			
		||||
          {txFields.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 = typeof _value === "object" ? _value.value : _value;
 | 
			
		||||
            value =
 | 
			
		||||
              typeof value === "object"
 | 
			
		||||
                ? JSON.stringify(value)
 | 
			
		||||
                : value?.toLocaleString();
 | 
			
		||||
            let isCurrency =
 | 
			
		||||
              typeof _value === "object" && _value.type === "currency";
 | 
			
		||||
            return (
 | 
			
		||||
              <Flex
 | 
			
		||||
                key={field}
 | 
			
		||||
                row
 | 
			
		||||
                fluid
 | 
			
		||||
                css={{
 | 
			
		||||
                  justifyContent: "flex-end",
 | 
			
		||||
                  alignItems: "center",
 | 
			
		||||
                  mb: "$3",
 | 
			
		||||
                  pr: "1px",
 | 
			
		||||
                }}
 | 
			
		||||
              >
 | 
			
		||||
                <Text muted css={{ mr: "$3" }}>
 | 
			
		||||
                  {field + (isCurrency ? " (XRP)" : "")}:{" "}
 | 
			
		||||
                </Text>
 | 
			
		||||
                <Input
 | 
			
		||||
                  value={value}
 | 
			
		||||
                  onChange={e =>
 | 
			
		||||
                    setState({
 | 
			
		||||
                      txFields: {
 | 
			
		||||
                        ...txFields,
 | 
			
		||||
                        [field]:
 | 
			
		||||
                          typeof _value === "object"
 | 
			
		||||
                            ? { ..._value, value: e.target.value }
 | 
			
		||||
                            : e.target.value,
 | 
			
		||||
                      },
 | 
			
		||||
                    })
 | 
			
		||||
                  }
 | 
			
		||||
                  css={{ width: "70%", flex: "inherit" }}
 | 
			
		||||
                />
 | 
			
		||||
              </Flex>
 | 
			
		||||
            );
 | 
			
		||||
          })}
 | 
			
		||||
        </Flex>
 | 
			
		||||
      </Container>
 | 
			
		||||
      <Flex
 | 
			
		||||
        row
 | 
			
		||||
        css={{
 | 
			
		||||
          justifyContent: "space-between",
 | 
			
		||||
          position: "absolute",
 | 
			
		||||
          left: 0,
 | 
			
		||||
          bottom: 0,
 | 
			
		||||
          width: "100%",
 | 
			
		||||
          mb: "$1"
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <Button outline>VIEW AS JSON</Button>
 | 
			
		||||
        <Flex row>
 | 
			
		||||
          <Button onClick={resetState} outline css={{ mr: "$3" }}>
 | 
			
		||||
            RESET
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button
 | 
			
		||||
            variant="primary"
 | 
			
		||||
            onClick={submitTest}
 | 
			
		||||
            isLoading={txIsLoading}
 | 
			
		||||
            disabled={txIsDisabled}
 | 
			
		||||
          >
 | 
			
		||||
            <Play weight="bold" size="16px" />
 | 
			
		||||
            RUN TEST
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Flex>
 | 
			
		||||
      </Flex>
 | 
			
		||||
    </Box>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Transaction;
 | 
			
		||||
							
								
								
									
										141
									
								
								components/Transaction/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								components/Transaction/index.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
			
		||||
import { Play } from "phosphor-react";
 | 
			
		||||
import { FC, useCallback, useState } from "react";
 | 
			
		||||
import { useSnapshot } from "valtio";
 | 
			
		||||
import state from "../../state";
 | 
			
		||||
import {
 | 
			
		||||
  modifyTransaction,
 | 
			
		||||
  prepareTransaction,
 | 
			
		||||
  TransactionState,
 | 
			
		||||
} from "../../state/transactions";
 | 
			
		||||
import { sendTransaction } from "../../state/actions";
 | 
			
		||||
import Box from "../Box";
 | 
			
		||||
import Button from "../Button";
 | 
			
		||||
import Flex from "../Flex";
 | 
			
		||||
import { TxJson } from "./json";
 | 
			
		||||
import { TxUI } from "./ui";
 | 
			
		||||
 | 
			
		||||
export interface TransactionProps {
 | 
			
		||||
  header: string;
 | 
			
		||||
  state: TransactionState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const Transaction: FC<TransactionProps> = ({
 | 
			
		||||
  header,
 | 
			
		||||
  state: txState,
 | 
			
		||||
  ...props
 | 
			
		||||
}) => {
 | 
			
		||||
  const { accounts } = useSnapshot(state);
 | 
			
		||||
  const {
 | 
			
		||||
    selectedAccount,
 | 
			
		||||
    selectedDestAccount,
 | 
			
		||||
    selectedTransaction,
 | 
			
		||||
    txFields,
 | 
			
		||||
    txIsDisabled,
 | 
			
		||||
    txIsLoading,
 | 
			
		||||
  } = txState;
 | 
			
		||||
 | 
			
		||||
  const [viewType, setViewType] = useState<"ui" | "json">("ui");
 | 
			
		||||
 | 
			
		||||
  const setState = useCallback(
 | 
			
		||||
    (pTx?: Partial<TransactionState>) => {
 | 
			
		||||
      modifyTransaction(header, pTx);
 | 
			
		||||
    },
 | 
			
		||||
    [header]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const prepareOptions = useCallback(() => {
 | 
			
		||||
    const TransactionType = selectedTransaction?.value;
 | 
			
		||||
    const Destination = selectedDestAccount?.value;
 | 
			
		||||
    const Account = selectedAccount?.value;
 | 
			
		||||
 | 
			
		||||
    return prepareTransaction({
 | 
			
		||||
      ...txFields,
 | 
			
		||||
      TransactionType,
 | 
			
		||||
      Destination,
 | 
			
		||||
      Account,
 | 
			
		||||
    });
 | 
			
		||||
  }, [
 | 
			
		||||
    selectedAccount?.value,
 | 
			
		||||
    selectedDestAccount?.value,
 | 
			
		||||
    selectedTransaction?.value,
 | 
			
		||||
    txFields,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const submitTest = useCallback(async () => {
 | 
			
		||||
    const account = accounts.find(
 | 
			
		||||
      acc => acc.address === selectedAccount?.value
 | 
			
		||||
    );
 | 
			
		||||
    const TransactionType = selectedTransaction?.value;
 | 
			
		||||
    if (!account || !TransactionType || txIsDisabled) return;
 | 
			
		||||
 | 
			
		||||
    setState({ txIsLoading: true });
 | 
			
		||||
    try {
 | 
			
		||||
      const options = prepareOptions();
 | 
			
		||||
      const logPrefix = header ? `${header.split(".")[0]}: ` : undefined;
 | 
			
		||||
 | 
			
		||||
      await sendTransaction(account, options, { logPrefix });
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.error(error);
 | 
			
		||||
      if (error instanceof Error) {
 | 
			
		||||
        state.transactionLogs.push({ type: "error", message: error.message });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    setState({ txIsLoading: false });
 | 
			
		||||
  }, [
 | 
			
		||||
    accounts,
 | 
			
		||||
    selectedTransaction?.value,
 | 
			
		||||
    txIsDisabled,
 | 
			
		||||
    setState,
 | 
			
		||||
    prepareOptions,
 | 
			
		||||
    selectedAccount?.value,
 | 
			
		||||
    header,
 | 
			
		||||
  ]);
 | 
			
		||||
 | 
			
		||||
  const resetState = useCallback(() => {
 | 
			
		||||
    setState({});
 | 
			
		||||
  }, [setState]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Box css={{ position: "relative", height: "calc(100% - 28px)" }} {...props}>
 | 
			
		||||
      {viewType === "json" ? (
 | 
			
		||||
        <TxJson prepareOptions={prepareOptions} />
 | 
			
		||||
      ) : (
 | 
			
		||||
        <TxUI state={txState} setState={setState} />
 | 
			
		||||
      )}
 | 
			
		||||
      <Flex
 | 
			
		||||
        row
 | 
			
		||||
        css={{
 | 
			
		||||
          justifyContent: "space-between",
 | 
			
		||||
          position: "absolute",
 | 
			
		||||
          left: 0,
 | 
			
		||||
          bottom: 0,
 | 
			
		||||
          width: "100%",
 | 
			
		||||
          mb: "$1",
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <Button
 | 
			
		||||
          onClick={() => setViewType(viewType === "ui" ? "json" : "ui")}
 | 
			
		||||
          outline
 | 
			
		||||
        >
 | 
			
		||||
          {viewType === "ui" ? "VIEW AS JSON" : "EXIT JSON VIEW"}
 | 
			
		||||
        </Button>
 | 
			
		||||
        <Flex row>
 | 
			
		||||
          <Button onClick={resetState} outline css={{ mr: "$3" }}>
 | 
			
		||||
            RESET
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button
 | 
			
		||||
            variant="primary"
 | 
			
		||||
            onClick={submitTest}
 | 
			
		||||
            isLoading={txIsLoading}
 | 
			
		||||
            disabled={txIsDisabled}
 | 
			
		||||
          >
 | 
			
		||||
            <Play weight="bold" size="16px" />
 | 
			
		||||
            RUN TEST
 | 
			
		||||
          </Button>
 | 
			
		||||
        </Flex>
 | 
			
		||||
      </Flex>
 | 
			
		||||
    </Box>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Transaction;
 | 
			
		||||
							
								
								
									
										56
									
								
								components/Transaction/json.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								components/Transaction/json.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
import Editor, { loader } from "@monaco-editor/react";
 | 
			
		||||
import { FC, useMemo } from 'react';
 | 
			
		||||
import { useTheme } from "next-themes";
 | 
			
		||||
 | 
			
		||||
import dark from "../../theme/editor/amy.json";
 | 
			
		||||
import light from "../../theme/editor/xcode_default.json";
 | 
			
		||||
import { useSnapshot } from 'valtio';
 | 
			
		||||
import state from '../../state';
 | 
			
		||||
 | 
			
		||||
loader.config({
 | 
			
		||||
  paths: {
 | 
			
		||||
    vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.30.1/min/vs",
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
interface JsonProps {
 | 
			
		||||
  prepareOptions?: () => any
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const TxJson: FC<JsonProps> = ({ prepareOptions }) => {
 | 
			
		||||
  const { editorSettings } = useSnapshot(state);
 | 
			
		||||
 | 
			
		||||
  const { theme } = useTheme();
 | 
			
		||||
 | 
			
		||||
  const value = useMemo(() => {
 | 
			
		||||
    return JSON.stringify(
 | 
			
		||||
      prepareOptions?.() || {},
 | 
			
		||||
      null,
 | 
			
		||||
      editorSettings.tabSize
 | 
			
		||||
    );
 | 
			
		||||
  }, [editorSettings.tabSize, prepareOptions])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Editor
 | 
			
		||||
      className="hooks-editor"
 | 
			
		||||
      language={"json"}
 | 
			
		||||
      height="calc(100% - 45px)"
 | 
			
		||||
      beforeMount={monaco => {
 | 
			
		||||
        monaco.editor.defineTheme("dark", dark as any);
 | 
			
		||||
        monaco.editor.defineTheme("light", light as any);
 | 
			
		||||
      }}
 | 
			
		||||
      value={value}
 | 
			
		||||
      onMount={(editor, monaco) => {
 | 
			
		||||
        editor.updateOptions({
 | 
			
		||||
          minimap: { enabled: false },
 | 
			
		||||
          glyphMargin: true,
 | 
			
		||||
          tabSize: editorSettings.tabSize,
 | 
			
		||||
          dragAndDrop: true,
 | 
			
		||||
          fontSize: 14,
 | 
			
		||||
          readOnly: true,
 | 
			
		||||
        });
 | 
			
		||||
      }}
 | 
			
		||||
      theme={theme === "dark" ? "dark" : "light"}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										243
									
								
								components/Transaction/ui.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								components/Transaction/ui.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,243 @@
 | 
			
		||||
import { FC, useEffect } 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,
 | 
			
		||||
  transactionsData,
 | 
			
		||||
  TxFields,
 | 
			
		||||
  OtherFields,
 | 
			
		||||
} from "../../state/transactions";
 | 
			
		||||
import { useSnapshot } from "valtio";
 | 
			
		||||
import state from "../../state";
 | 
			
		||||
import { streamState } from "../DebugStream";
 | 
			
		||||
 | 
			
		||||
interface UIProps {
 | 
			
		||||
  setState: (pTx?: Partial<TransactionState> | undefined) => void;
 | 
			
		||||
  state: TransactionState;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const TxUI: FC<UIProps> = ({ state: txState, setState }) => {
 | 
			
		||||
  const { accounts } = useSnapshot(state);
 | 
			
		||||
  const {
 | 
			
		||||
    selectedAccount,
 | 
			
		||||
    selectedDestAccount,
 | 
			
		||||
    selectedTransaction,
 | 
			
		||||
    txFields,
 | 
			
		||||
    txIsLoading,
 | 
			
		||||
  } = txState;
 | 
			
		||||
 | 
			
		||||
  const handleSetAccount = (acc: SelectOption) => {
 | 
			
		||||
    setState({ selectedAccount: acc });
 | 
			
		||||
    streamState.selectedAccount = acc;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const transactionsOptions = transactionsData.map(tx => ({
 | 
			
		||||
    value: tx.TransactionType,
 | 
			
		||||
    label: tx.TransactionType,
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  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);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const transactionType = selectedTransaction?.value;
 | 
			
		||||
    const account = accounts.find(
 | 
			
		||||
      acc => acc.address === selectedAccount?.value
 | 
			
		||||
    );
 | 
			
		||||
    if (!account || !transactionType || txIsLoading) {
 | 
			
		||||
      setState({ txIsDisabled: true });
 | 
			
		||||
    } else {
 | 
			
		||||
      setState({ txIsDisabled: false });
 | 
			
		||||
    }
 | 
			
		||||
  }, [txIsLoading, selectedTransaction, selectedAccount, accounts, setState]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const txFields: TxFields | undefined = transactionsData.find(
 | 
			
		||||
      tx => tx.TransactionType === selectedTransaction?.value
 | 
			
		||||
    );
 | 
			
		||||
    if (!txFields) return setState({ txFields: {} });
 | 
			
		||||
 | 
			
		||||
    const _txFields = Object.keys(txFields)
 | 
			
		||||
      .filter(key => !["TransactionType", "Account", "Sequence"].includes(key))
 | 
			
		||||
      .reduce<TxFields>(
 | 
			
		||||
        (tf, key) => ((tf[key as keyof TxFields] = (txFields as any)[key]), tf),
 | 
			
		||||
        {}
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
    if (!_txFields.Destination) setState({ selectedDestAccount: null });
 | 
			
		||||
    setState({ txFields: _txFields });
 | 
			
		||||
  }, [setState, selectedTransaction]);
 | 
			
		||||
 | 
			
		||||
  const usualFields = ["TransactionType", "Amount", "Account", "Destination"];
 | 
			
		||||
 | 
			
		||||
  const otherFields = Object.keys(txFields).filter(
 | 
			
		||||
    k => !usualFields.includes(k)
 | 
			
		||||
  ) as OtherFields;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Container
 | 
			
		||||
      css={{
 | 
			
		||||
        p: "$3 01",
 | 
			
		||||
        fontSize: "$sm",
 | 
			
		||||
        height: "calc(100% - 45px)",
 | 
			
		||||
      }}
 | 
			
		||||
    >
 | 
			
		||||
      <Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
 | 
			
		||||
        <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={(tx: any) => setState({ selectedTransaction: tx })}
 | 
			
		||||
          />
 | 
			
		||||
        </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>
 | 
			
		||||
        {txFields.Amount !== undefined && (
 | 
			
		||||
          <Flex
 | 
			
		||||
            row
 | 
			
		||||
            fluid
 | 
			
		||||
            css={{
 | 
			
		||||
              justifyContent: "flex-end",
 | 
			
		||||
              alignItems: "center",
 | 
			
		||||
              mb: "$3",
 | 
			
		||||
              pr: "1px",
 | 
			
		||||
            }}
 | 
			
		||||
          >
 | 
			
		||||
            <Text muted css={{ mr: "$3" }}>
 | 
			
		||||
              Amount (XRP):{" "}
 | 
			
		||||
            </Text>
 | 
			
		||||
            <Input
 | 
			
		||||
              value={txFields.Amount.value}
 | 
			
		||||
              onChange={e =>
 | 
			
		||||
                setState({
 | 
			
		||||
                  txFields: {
 | 
			
		||||
                    ...txFields,
 | 
			
		||||
                    Amount: { type: "currency", value: e.target.value },
 | 
			
		||||
                  },
 | 
			
		||||
                })
 | 
			
		||||
              }
 | 
			
		||||
              css={{ width: "70%", flex: "inherit" }}
 | 
			
		||||
            />
 | 
			
		||||
          </Flex>
 | 
			
		||||
        )}
 | 
			
		||||
        {txFields.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 = typeof _value === "object" ? _value.value : _value;
 | 
			
		||||
          value =
 | 
			
		||||
            typeof value === "object"
 | 
			
		||||
              ? JSON.stringify(value)
 | 
			
		||||
              : value?.toLocaleString();
 | 
			
		||||
          let isCurrency =
 | 
			
		||||
            typeof _value === "object" && _value.type === "currency";
 | 
			
		||||
          return (
 | 
			
		||||
            <Flex
 | 
			
		||||
              key={field}
 | 
			
		||||
              row
 | 
			
		||||
              fluid
 | 
			
		||||
              css={{
 | 
			
		||||
                justifyContent: "flex-end",
 | 
			
		||||
                alignItems: "center",
 | 
			
		||||
                mb: "$3",
 | 
			
		||||
                pr: "1px",
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <Text muted css={{ mr: "$3" }}>
 | 
			
		||||
                {field + (isCurrency ? " (XRP)" : "")}:{" "}
 | 
			
		||||
              </Text>
 | 
			
		||||
              <Input
 | 
			
		||||
                value={value}
 | 
			
		||||
                onChange={e =>
 | 
			
		||||
                  setState({
 | 
			
		||||
                    txFields: {
 | 
			
		||||
                      ...txFields,
 | 
			
		||||
                      [field]:
 | 
			
		||||
                        typeof _value === "object"
 | 
			
		||||
                          ? { ..._value, value: e.target.value }
 | 
			
		||||
                          : e.target.value,
 | 
			
		||||
                    },
 | 
			
		||||
                  })
 | 
			
		||||
                }
 | 
			
		||||
                css={{ width: "70%", flex: "inherit" }}
 | 
			
		||||
              />
 | 
			
		||||
            </Flex>
 | 
			
		||||
          );
 | 
			
		||||
        })}
 | 
			
		||||
      </Flex>
 | 
			
		||||
    </Container>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
@@ -1,6 +1,28 @@
 | 
			
		||||
import { proxy } from 'valtio';
 | 
			
		||||
import { TransactionState } from '../components/Transaction';
 | 
			
		||||
import { deepEqual } from '../utils/object';
 | 
			
		||||
import transactionsData from "../content/transactions.json";
 | 
			
		||||
 | 
			
		||||
export type SelectOption = {
 | 
			
		||||
    value: string;
 | 
			
		||||
    label: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface TransactionState {
 | 
			
		||||
    selectedTransaction: SelectOption | null;
 | 
			
		||||
    selectedAccount: SelectOption | null;
 | 
			
		||||
    selectedDestAccount: SelectOption | null;
 | 
			
		||||
    txIsLoading: boolean;
 | 
			
		||||
    txIsDisabled: boolean;
 | 
			
		||||
    txFields: TxFields;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type TxFields = Omit<
 | 
			
		||||
    typeof transactionsData[0],
 | 
			
		||||
    "Account" | "Sequence" | "TransactionType"
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
export type OtherFields = (keyof Omit<TxFields, "Destination">)[];
 | 
			
		||||
 | 
			
		||||
export const defaultTransaction: TransactionState = {
 | 
			
		||||
    selectedTransaction: null,
 | 
			
		||||
@@ -50,10 +72,55 @@ export const modifyTransaction = (
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (deepEqual(partialTx, {})) {
 | 
			
		||||
        tx.state = { ...defaultTransaction }
 | 
			
		||||
        console.log({ tx: tx.state, is: tx.state === defaultTransaction })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Object.keys(partialTx).forEach(k => {
 | 
			
		||||
        // Typescript mess here, but is definetly safe!
 | 
			
		||||
        const s = tx.state as any;
 | 
			
		||||
        const p = partialTx as any;
 | 
			
		||||
        if (!deepEqual(s[k], p[k])) s[k] = p[k];
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const prepareTransaction = (data: any) => {
 | 
			
		||||
    let options = { ...data };
 | 
			
		||||
 | 
			
		||||
    // options.Destination = selectedDestAccount?.value;
 | 
			
		||||
    (Object.keys(options)).forEach(field => {
 | 
			
		||||
        let _value = options[field];
 | 
			
		||||
        // convert currency
 | 
			
		||||
        if (typeof _value === "object" && _value.type === "currency") {
 | 
			
		||||
            if (+_value.value) {
 | 
			
		||||
                options[field] = (+_value.value * 1000000 + "") as any;
 | 
			
		||||
            } else {
 | 
			
		||||
                options[field] = undefined; // 👇 💀
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // handle type: `json`
 | 
			
		||||
        if (typeof _value === "object" && _value.type === "json") {
 | 
			
		||||
            if (typeof _value.value === "object") {
 | 
			
		||||
                options[field] = _value.value as any;
 | 
			
		||||
            } else {
 | 
			
		||||
                try {
 | 
			
		||||
                    options[field] = JSON.parse(_value.value);
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    const message = `Input error for json field '${field}': ${error instanceof Error ? error.message : ""
 | 
			
		||||
                        }`;
 | 
			
		||||
                    throw Error(message);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // delete unneccesary fields
 | 
			
		||||
        if (!options[field]) {
 | 
			
		||||
            delete options[field];
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return options
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { transactionsData }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user