446 lines
13 KiB
TypeScript
446 lines
13 KiB
TypeScript
import {
|
|
Container,
|
|
Flex,
|
|
Box,
|
|
Tabs,
|
|
Tab,
|
|
Input,
|
|
Select,
|
|
Text,
|
|
Button,
|
|
} from "../../components";
|
|
import { Play } from "phosphor-react";
|
|
import dynamic from "next/dynamic";
|
|
import { useSnapshot } from "valtio";
|
|
import Split from "react-split";
|
|
import state from "../../state";
|
|
import { sendTransaction } from "../../state/actions";
|
|
import { useCallback, useEffect, useState, FC } from "react";
|
|
import transactionsData from "../../content/transactions.json";
|
|
|
|
const DebugStream = dynamic(() => import("../../components/DebugStream"), {
|
|
ssr: false,
|
|
});
|
|
|
|
const LogBox = dynamic(() => import("../../components/LogBox"), {
|
|
ssr: false,
|
|
});
|
|
const Accounts = dynamic(() => import("../../components/Accounts"), {
|
|
ssr: false,
|
|
});
|
|
|
|
// type SelectOption<T> = { value: T, label: string };
|
|
type TxFields = Omit<
|
|
typeof transactionsData[0],
|
|
"Account" | "Sequence" | "TransactionType"
|
|
>;
|
|
type OtherFields = (keyof Omit<TxFields, "Destination">)[];
|
|
|
|
interface Props {
|
|
header?: string;
|
|
}
|
|
|
|
const Transaction: FC<Props> = ({ header, ...props }) => {
|
|
const snap = useSnapshot(state);
|
|
|
|
const transactionsOptions = transactionsData.map((tx) => ({
|
|
value: tx.TransactionType,
|
|
label: tx.TransactionType,
|
|
}));
|
|
const [selectedTransaction, setSelectedTransaction] = useState<
|
|
typeof transactionsOptions[0] | null
|
|
>(null);
|
|
|
|
const accountOptions = snap.accounts.map((acc) => ({
|
|
label: acc.name,
|
|
value: acc.address,
|
|
}));
|
|
const [selectedAccount, setSelectedAccount] = useState<
|
|
typeof accountOptions[0] | null
|
|
>(null);
|
|
|
|
const destAccountOptions = snap.accounts
|
|
.map((acc) => ({
|
|
label: acc.name,
|
|
value: acc.address,
|
|
}))
|
|
.filter((acc) => acc.value !== selectedAccount?.value);
|
|
const [selectedDestAccount, setSelectedDestAccount] = useState<
|
|
typeof destAccountOptions[0] | null
|
|
>(null);
|
|
|
|
const [txIsLoading, setTxIsLoading] = useState(false);
|
|
const [txIsDisabled, setTxIsDisabled] = useState(false);
|
|
const [txFields, setTxFields] = useState<TxFields>({});
|
|
|
|
useEffect(() => {
|
|
const transactionType = selectedTransaction?.value;
|
|
const account = snap.accounts.find(
|
|
(acc) => acc.address === selectedAccount?.value
|
|
);
|
|
if (!account || !transactionType || txIsLoading) {
|
|
setTxIsDisabled(true);
|
|
} else {
|
|
setTxIsDisabled(false);
|
|
}
|
|
}, [txIsLoading, selectedTransaction, selectedAccount, snap.accounts]);
|
|
|
|
useEffect(() => {
|
|
let _txFields: TxFields | undefined = transactionsData.find(
|
|
(tx) => tx.TransactionType === selectedTransaction?.value
|
|
);
|
|
if (!_txFields) return setTxFields({});
|
|
_txFields = { ..._txFields } as TxFields;
|
|
|
|
setSelectedDestAccount(null);
|
|
// @ts-ignore
|
|
delete _txFields.TransactionType;
|
|
// @ts-ignore
|
|
delete _txFields.Account;
|
|
// @ts-ignore
|
|
delete _txFields.Sequence;
|
|
setTxFields(_txFields);
|
|
}, [selectedTransaction, setSelectedDestAccount]);
|
|
|
|
const submitTest = useCallback(async () => {
|
|
const account = snap.accounts.find(
|
|
(acc) => acc.address === selectedAccount?.value
|
|
);
|
|
const TransactionType = selectedTransaction?.value;
|
|
if (!account || !TransactionType || txIsDisabled) return;
|
|
|
|
setTxIsLoading(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 });
|
|
}
|
|
}
|
|
setTxIsLoading(false);
|
|
}, [
|
|
header,
|
|
selectedAccount?.value,
|
|
selectedDestAccount?.value,
|
|
selectedTransaction?.value,
|
|
snap.accounts,
|
|
txFields,
|
|
txIsDisabled,
|
|
]);
|
|
|
|
const resetState = useCallback(() => {
|
|
setSelectedAccount(null);
|
|
setSelectedDestAccount(null);
|
|
setSelectedTransaction(null);
|
|
setTxFields({});
|
|
setTxIsDisabled(false);
|
|
setTxIsLoading(false);
|
|
}, []);
|
|
|
|
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 0", fontSize: "$sm", height: "calc(100% - 28px)" }}
|
|
>
|
|
<Flex column fluid css={{ height: "100%", overflowY: "auto" }}>
|
|
<Flex
|
|
row
|
|
fluid
|
|
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
|
|
>
|
|
<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) => setSelectedTransaction(tt as any)}
|
|
/>
|
|
</Flex>
|
|
<Flex
|
|
row
|
|
fluid
|
|
css={{ justifyContent: "flex-end", alignItems: "center", mb: "$3" }}
|
|
>
|
|
<Text muted css={{ mr: "$3" }}>
|
|
Account:{" "}
|
|
</Text>
|
|
<Select
|
|
instanceId="from-account"
|
|
placeholder="Select your account"
|
|
css={{ width: "70%" }}
|
|
options={accountOptions}
|
|
value={selectedAccount}
|
|
onChange={(acc) => setSelectedAccount(acc as any)}
|
|
/>
|
|
</Flex>
|
|
{txFields.Amount !== undefined && (
|
|
<Flex
|
|
row
|
|
fluid
|
|
css={{
|
|
justifyContent: "flex-end",
|
|
alignItems: "center",
|
|
mb: "$3",
|
|
}}
|
|
>
|
|
<Text muted css={{ mr: "$3" }}>
|
|
Amount (XRP):{" "}
|
|
</Text>
|
|
<Input
|
|
value={txFields.Amount.value}
|
|
onChange={(e) =>
|
|
setTxFields({
|
|
...txFields,
|
|
Amount: { type: "currency", value: e.target.value },
|
|
})
|
|
}
|
|
variant="deep"
|
|
css={{ width: "70%", flex: "inherit", height: "$9" }}
|
|
/>
|
|
</Flex>
|
|
)}
|
|
{txFields.Destination !== undefined && (
|
|
<Flex
|
|
row
|
|
fluid
|
|
css={{
|
|
justifyContent: "flex-end",
|
|
alignItems: "center",
|
|
mb: "$3",
|
|
}}
|
|
>
|
|
<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) => setSelectedDestAccount(acc as any)}
|
|
/>
|
|
</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",
|
|
}}
|
|
>
|
|
<Text muted css={{ mr: "$3" }}>
|
|
{field + (isCurrency ? " (XRP)" : "")}:{" "}
|
|
</Text>
|
|
<Input
|
|
value={value}
|
|
onChange={(e) =>
|
|
setTxFields({
|
|
...txFields,
|
|
[field]:
|
|
typeof _value === "object"
|
|
? { ..._value, value: e.target.value }
|
|
: e.target.value,
|
|
})
|
|
}
|
|
variant="deep"
|
|
css={{ width: "70%", flex: "inherit", height: "$9" }}
|
|
/>
|
|
</Flex>
|
|
);
|
|
})}
|
|
</Flex>
|
|
</Container>
|
|
<Flex
|
|
row
|
|
css={{
|
|
justifyContent: "space-between",
|
|
position: "absolute",
|
|
left: 0,
|
|
bottom: 0,
|
|
width: "100%",
|
|
}}
|
|
>
|
|
<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>
|
|
);
|
|
};
|
|
|
|
const Test = () => {
|
|
const snap = useSnapshot(state);
|
|
const [tabHeaders, setTabHeaders] = useState<string[]>(["test1.json"]);
|
|
return (
|
|
<Container css={{ px: 0 }}>
|
|
<Split
|
|
direction="vertical"
|
|
sizes={[50, 50]}
|
|
gutterSize={4}
|
|
gutterAlign="center"
|
|
style={{ height: "calc(100vh - 60px)" }}
|
|
>
|
|
<Flex
|
|
row
|
|
fluid
|
|
css={{
|
|
justifyContent: "center",
|
|
p: "$3 $2",
|
|
}}
|
|
>
|
|
<Split
|
|
direction="horizontal"
|
|
sizes={[50, 50]}
|
|
minSize={[180, 320]}
|
|
gutterSize={4}
|
|
gutterAlign="center"
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "row",
|
|
width: "100%",
|
|
height: "100%",
|
|
}}
|
|
>
|
|
<Box css={{ width: "55%", px: "$2" }}>
|
|
<Tabs
|
|
keepAllAlive
|
|
forceDefaultExtension
|
|
defaultExtension=".json"
|
|
onCreateNewTab={(name) =>
|
|
setTabHeaders(tabHeaders.concat(name))
|
|
}
|
|
onCloseTab={(index) =>
|
|
setTabHeaders(tabHeaders.filter((_, idx) => idx !== index))
|
|
}
|
|
>
|
|
{tabHeaders.map((header) => (
|
|
<Tab key={header} header={header}>
|
|
<Transaction header={header} />
|
|
</Tab>
|
|
))}
|
|
</Tabs>
|
|
</Box>
|
|
<Box css={{ width: "45%", mx: "$2", height: "100%" }}>
|
|
<Accounts card hideDeployBtn showHookStats />
|
|
</Box>
|
|
</Split>
|
|
</Flex>
|
|
|
|
<Flex row fluid>
|
|
<Split
|
|
direction="horizontal"
|
|
sizes={[50, 50]}
|
|
minSize={[320, 160]}
|
|
gutterSize={4}
|
|
gutterAlign="center"
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "row",
|
|
width: "100%",
|
|
height: "100%",
|
|
}}
|
|
>
|
|
<Box
|
|
css={{
|
|
borderRight: "1px solid $mauve8",
|
|
height: "100%",
|
|
}}
|
|
>
|
|
<LogBox
|
|
title="Development Log"
|
|
logs={snap.transactionLogs}
|
|
clearLog={() => (state.transactionLogs = [])}
|
|
/>
|
|
</Box>
|
|
<Box css={{ height: "100%" }}>
|
|
<DebugStream />
|
|
</Box>
|
|
</Split>
|
|
</Flex>
|
|
</Split>
|
|
</Container>
|
|
);
|
|
};
|
|
|
|
export default Test;
|