Add signature input to tx table (#9)
This commit is contained in:
parent
82543886fb
commit
237b3ae025
|
@ -1595,6 +1595,14 @@
|
|||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@types/bs58": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz",
|
||||
"integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==",
|
||||
"requires": {
|
||||
"base-x": "^3.0.6"
|
||||
}
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/jest": "^24.0.0",
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"bootstrap": "^4.4.1",
|
||||
"bs58": "^4.0.1",
|
||||
"node-sass": "^4.13.1",
|
||||
"prettier": "^1.19.1",
|
||||
"react": "^16.13.0",
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
NETWORKS,
|
||||
Network
|
||||
} from "../providers/network";
|
||||
import { assertUnreachable } from "../utils";
|
||||
|
||||
type Props = {
|
||||
show: boolean;
|
||||
|
@ -103,6 +104,8 @@ function NetworkToggle() {
|
|||
case NetworkStatus.Failure:
|
||||
activeSuffix = "danger";
|
||||
break;
|
||||
default:
|
||||
assertUnreachable(status);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,12 +1,47 @@
|
|||
import React from "react";
|
||||
import {
|
||||
useTransactions,
|
||||
useTransactionsDispatch,
|
||||
checkTransactionStatus,
|
||||
ActionType,
|
||||
Transaction,
|
||||
Status
|
||||
} from "../providers/transactions";
|
||||
import bs58 from "bs58";
|
||||
import { assertUnreachable } from "../utils";
|
||||
import { useNetwork } from "../providers/network";
|
||||
|
||||
function TransactionsCard() {
|
||||
const { transactions } = useTransactions();
|
||||
const { transactions, idCounter } = useTransactions();
|
||||
const dispatch = useTransactionsDispatch();
|
||||
const signatureInput = React.useRef<HTMLInputElement>(null);
|
||||
const [error, setError] = React.useState("");
|
||||
const { url } = useNetwork();
|
||||
|
||||
const onNew = (signature: string) => {
|
||||
if (signature.length === 0) return;
|
||||
try {
|
||||
const length = bs58.decode(signature).length;
|
||||
if (length > 64) {
|
||||
setError("Signature is too short");
|
||||
return;
|
||||
} else if (length < 64) {
|
||||
setError("Signature is too short");
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
setError(`${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch({ type: ActionType.InputSignature, signature });
|
||||
checkTransactionStatus(dispatch, idCounter + 1, signature, url);
|
||||
|
||||
const inputEl = signatureInput.current;
|
||||
if (inputEl) {
|
||||
inputEl.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
|
@ -16,6 +51,9 @@ function TransactionsCard() {
|
|||
<table className="table table-sm table-nowrap card-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-muted text-center">
|
||||
<span className="fe fe-hash"></span>
|
||||
</th>
|
||||
<th className="text-muted">Status</th>
|
||||
<th className="text-muted">Signature</th>
|
||||
<th className="text-muted">Confirmations</th>
|
||||
|
@ -23,9 +61,36 @@ function TransactionsCard() {
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody className="list">
|
||||
{Object.values(transactions).map(transaction =>
|
||||
renderTransactionRow(transaction)
|
||||
)}
|
||||
<tr>
|
||||
<td>
|
||||
<span className="badge badge-soft-dark badge-pill">
|
||||
{idCounter + 1}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span className={`badge badge-soft-primary`}>New</span>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
onInput={() => setError("")}
|
||||
onKeyDown={e =>
|
||||
e.keyCode === 13 && onNew(e.currentTarget.value)
|
||||
}
|
||||
onSubmit={e => onNew(e.currentTarget.value)}
|
||||
ref={signatureInput}
|
||||
className={`form-control text-signature text-monospace ${
|
||||
error ? "is-invalid" : ""
|
||||
}`}
|
||||
placeholder="abcd..."
|
||||
/>
|
||||
{error ? <div className="invalid-feedback">{error}</div> : null}
|
||||
</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
{transactions.map(transaction => renderTransactionRow(transaction))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -65,22 +130,29 @@ const renderTransactionRow = (transaction: Transaction) => {
|
|||
statusClass = "danger";
|
||||
statusText = "Failed";
|
||||
break;
|
||||
case Status.Pending:
|
||||
case Status.Missing:
|
||||
statusClass = "warning";
|
||||
statusText = "Pending";
|
||||
statusText = "Not Found";
|
||||
break;
|
||||
default:
|
||||
return assertUnreachable(transaction.status);
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={transaction.signature}>
|
||||
<td>
|
||||
<span className="badge badge-soft-dark badge-pill">
|
||||
{transaction.id}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span className={`badge badge-soft-${statusClass}`}>{statusText}</span>
|
||||
</td>
|
||||
<td>
|
||||
<code>{transaction.signature}</code>
|
||||
</td>
|
||||
<td>TODO</td>
|
||||
<td>TODO</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -133,7 +133,7 @@ export function NetworkProvider({ children }: NetworkProviderProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export function networkUrl(network: Network, customUrl: string) {
|
||||
export function networkUrl(network: Network, customUrl: string): string {
|
||||
switch (network) {
|
||||
case Network.Devnet:
|
||||
return DEVNET_URL;
|
||||
|
|
|
@ -8,13 +8,18 @@ export enum Status {
|
|||
CheckFailed,
|
||||
Success,
|
||||
Failure,
|
||||
Pending
|
||||
Missing
|
||||
}
|
||||
|
||||
enum Source {
|
||||
Url,
|
||||
Input
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
id: number;
|
||||
status: Status;
|
||||
recent: boolean;
|
||||
source: Source;
|
||||
signature: TransactionSignature;
|
||||
}
|
||||
|
||||
|
@ -24,20 +29,52 @@ interface State {
|
|||
transactions: Transactions;
|
||||
}
|
||||
|
||||
export enum ActionType {
|
||||
UpdateStatus,
|
||||
InputSignature
|
||||
}
|
||||
|
||||
interface UpdateStatus {
|
||||
type: ActionType.UpdateStatus;
|
||||
id: number;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
type Action = UpdateStatus;
|
||||
interface InputSignature {
|
||||
type: ActionType.InputSignature;
|
||||
signature: TransactionSignature;
|
||||
}
|
||||
|
||||
type Action = UpdateStatus | InputSignature;
|
||||
type Dispatch = (action: Action) => void;
|
||||
|
||||
function reducer(state: State, action: Action): State {
|
||||
let transaction = state.transactions[action.id];
|
||||
if (transaction) {
|
||||
transaction = { ...transaction, status: action.status };
|
||||
const transactions = { ...state.transactions, [action.id]: transaction };
|
||||
return { ...state, transactions };
|
||||
switch (action.type) {
|
||||
case ActionType.InputSignature: {
|
||||
const idCounter = state.idCounter + 1;
|
||||
const transactions = {
|
||||
...state.transactions,
|
||||
[idCounter]: {
|
||||
id: idCounter,
|
||||
status: Status.Checking,
|
||||
source: Source.Input,
|
||||
signature: action.signature
|
||||
}
|
||||
};
|
||||
return { ...state, transactions, idCounter };
|
||||
}
|
||||
case ActionType.UpdateStatus: {
|
||||
let transaction = state.transactions[action.id];
|
||||
if (transaction) {
|
||||
transaction = { ...transaction, status: action.status };
|
||||
const transactions = {
|
||||
...state.transactions,
|
||||
[action.id]: transaction
|
||||
};
|
||||
return { ...state, transactions };
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
@ -51,7 +88,7 @@ function initState(): State {
|
|||
transactions[id] = {
|
||||
id,
|
||||
status: Status.Checking,
|
||||
recent: true,
|
||||
source: Source.Url,
|
||||
signature
|
||||
};
|
||||
return transactions;
|
||||
|
@ -72,9 +109,8 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
|||
|
||||
// Check transaction statuses on startup and whenever network updates
|
||||
React.useEffect(() => {
|
||||
const connection = new Connection(url);
|
||||
Object.values(state.transactions).forEach(tx => {
|
||||
checkTransactionStatus(dispatch, tx, connection);
|
||||
checkTransactionStatus(dispatch, tx.id, tx.signature, url);
|
||||
});
|
||||
}, [status, url]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
@ -89,23 +125,24 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
|||
|
||||
export async function checkTransactionStatus(
|
||||
dispatch: Dispatch,
|
||||
transaction: Transaction,
|
||||
connection: Connection
|
||||
id: number,
|
||||
signature: TransactionSignature,
|
||||
url: string
|
||||
) {
|
||||
const id = transaction.id;
|
||||
dispatch({
|
||||
type: ActionType.UpdateStatus,
|
||||
status: Status.Checking,
|
||||
id
|
||||
});
|
||||
|
||||
let status;
|
||||
try {
|
||||
const signatureStatus = await connection.getSignatureStatus(
|
||||
transaction.signature
|
||||
const signatureStatus = await new Connection(url).getSignatureStatus(
|
||||
signature
|
||||
);
|
||||
|
||||
if (signatureStatus === null) {
|
||||
status = Status.Pending;
|
||||
status = Status.Missing;
|
||||
} else if ("Ok" in signatureStatus) {
|
||||
status = Status.Success;
|
||||
} else {
|
||||
|
@ -115,7 +152,7 @@ export async function checkTransactionStatus(
|
|||
console.error("Failed to check transaction status", error);
|
||||
status = Status.CheckFailed;
|
||||
}
|
||||
dispatch({ status, id });
|
||||
dispatch({ type: ActionType.UpdateStatus, status, id });
|
||||
}
|
||||
|
||||
export function useTransactions() {
|
||||
|
@ -125,7 +162,12 @@ export function useTransactions() {
|
|||
`useTransactions must be used within a TransactionsProvider`
|
||||
);
|
||||
}
|
||||
return context;
|
||||
return {
|
||||
idCounter: context.idCounter,
|
||||
transactions: Object.values(context.transactions).sort((a, b) =>
|
||||
a.id <= b.id ? 1 : -1
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
export function useTransactionsDispatch() {
|
||||
|
|
|
@ -33,3 +33,11 @@ code {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-signature {
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
input.text-signature {
|
||||
padding: 0 0.75rem
|
||||
}
|
|
@ -10,3 +10,7 @@ export function findGetParameter(parameterName: string): string | null {
|
|||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
export function assertUnreachable(x: never): never {
|
||||
throw new Error("Unreachable!");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue