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"
|
"@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": {
|
"@types/color-name": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
"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/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.3.2",
|
"@testing-library/react": "^9.3.2",
|
||||||
"@testing-library/user-event": "^7.1.2",
|
"@testing-library/user-event": "^7.1.2",
|
||||||
|
"@types/bs58": "^4.0.1",
|
||||||
"@types/jest": "^24.0.0",
|
"@types/jest": "^24.0.0",
|
||||||
"@types/node": "^12.0.0",
|
"@types/node": "^12.0.0",
|
||||||
"@types/react": "^16.9.0",
|
"@types/react": "^16.9.0",
|
||||||
"@types/react-dom": "^16.9.0",
|
"@types/react-dom": "^16.9.0",
|
||||||
"bootstrap": "^4.4.1",
|
"bootstrap": "^4.4.1",
|
||||||
|
"bs58": "^4.0.1",
|
||||||
"node-sass": "^4.13.1",
|
"node-sass": "^4.13.1",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"react": "^16.13.0",
|
"react": "^16.13.0",
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
NETWORKS,
|
NETWORKS,
|
||||||
Network
|
Network
|
||||||
} from "../providers/network";
|
} from "../providers/network";
|
||||||
|
import { assertUnreachable } from "../utils";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
|
@ -103,6 +104,8 @@ function NetworkToggle() {
|
||||||
case NetworkStatus.Failure:
|
case NetworkStatus.Failure:
|
||||||
activeSuffix = "danger";
|
activeSuffix = "danger";
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
assertUnreachable(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,12 +1,47 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
useTransactions,
|
useTransactions,
|
||||||
|
useTransactionsDispatch,
|
||||||
|
checkTransactionStatus,
|
||||||
|
ActionType,
|
||||||
Transaction,
|
Transaction,
|
||||||
Status
|
Status
|
||||||
} from "../providers/transactions";
|
} from "../providers/transactions";
|
||||||
|
import bs58 from "bs58";
|
||||||
|
import { assertUnreachable } from "../utils";
|
||||||
|
import { useNetwork } from "../providers/network";
|
||||||
|
|
||||||
function TransactionsCard() {
|
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 (
|
return (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
|
@ -16,6 +51,9 @@ function TransactionsCard() {
|
||||||
<table className="table table-sm table-nowrap card-table">
|
<table className="table table-sm table-nowrap card-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="text-muted text-center">
|
||||||
|
<span className="fe fe-hash"></span>
|
||||||
|
</th>
|
||||||
<th className="text-muted">Status</th>
|
<th className="text-muted">Status</th>
|
||||||
<th className="text-muted">Signature</th>
|
<th className="text-muted">Signature</th>
|
||||||
<th className="text-muted">Confirmations</th>
|
<th className="text-muted">Confirmations</th>
|
||||||
|
@ -23,9 +61,36 @@ function TransactionsCard() {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="list">
|
<tbody className="list">
|
||||||
{Object.values(transactions).map(transaction =>
|
<tr>
|
||||||
renderTransactionRow(transaction)
|
<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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,22 +130,29 @@ const renderTransactionRow = (transaction: Transaction) => {
|
||||||
statusClass = "danger";
|
statusClass = "danger";
|
||||||
statusText = "Failed";
|
statusText = "Failed";
|
||||||
break;
|
break;
|
||||||
case Status.Pending:
|
case Status.Missing:
|
||||||
statusClass = "warning";
|
statusClass = "warning";
|
||||||
statusText = "Pending";
|
statusText = "Not Found";
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
return assertUnreachable(transaction.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={transaction.signature}>
|
<tr key={transaction.signature}>
|
||||||
|
<td>
|
||||||
|
<span className="badge badge-soft-dark badge-pill">
|
||||||
|
{transaction.id}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span className={`badge badge-soft-${statusClass}`}>{statusText}</span>
|
<span className={`badge badge-soft-${statusClass}`}>{statusText}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<code>{transaction.signature}</code>
|
<code>{transaction.signature}</code>
|
||||||
</td>
|
</td>
|
||||||
<td>TODO</td>
|
<td>-</td>
|
||||||
<td>TODO</td>
|
<td>-</td>
|
||||||
</tr>
|
</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) {
|
switch (network) {
|
||||||
case Network.Devnet:
|
case Network.Devnet:
|
||||||
return DEVNET_URL;
|
return DEVNET_URL;
|
||||||
|
|
|
@ -8,13 +8,18 @@ export enum Status {
|
||||||
CheckFailed,
|
CheckFailed,
|
||||||
Success,
|
Success,
|
||||||
Failure,
|
Failure,
|
||||||
Pending
|
Missing
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Source {
|
||||||
|
Url,
|
||||||
|
Input
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Transaction {
|
export interface Transaction {
|
||||||
id: number;
|
id: number;
|
||||||
status: Status;
|
status: Status;
|
||||||
recent: boolean;
|
source: Source;
|
||||||
signature: TransactionSignature;
|
signature: TransactionSignature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,20 +29,52 @@ interface State {
|
||||||
transactions: Transactions;
|
transactions: Transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ActionType {
|
||||||
|
UpdateStatus,
|
||||||
|
InputSignature
|
||||||
|
}
|
||||||
|
|
||||||
interface UpdateStatus {
|
interface UpdateStatus {
|
||||||
|
type: ActionType.UpdateStatus;
|
||||||
id: number;
|
id: number;
|
||||||
status: Status;
|
status: Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Action = UpdateStatus;
|
interface InputSignature {
|
||||||
|
type: ActionType.InputSignature;
|
||||||
|
signature: TransactionSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action = UpdateStatus | InputSignature;
|
||||||
type Dispatch = (action: Action) => void;
|
type Dispatch = (action: Action) => void;
|
||||||
|
|
||||||
function reducer(state: State, action: Action): State {
|
function reducer(state: State, action: Action): State {
|
||||||
let transaction = state.transactions[action.id];
|
switch (action.type) {
|
||||||
if (transaction) {
|
case ActionType.InputSignature: {
|
||||||
transaction = { ...transaction, status: action.status };
|
const idCounter = state.idCounter + 1;
|
||||||
const transactions = { ...state.transactions, [action.id]: transaction };
|
const transactions = {
|
||||||
return { ...state, 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;
|
return state;
|
||||||
}
|
}
|
||||||
|
@ -51,7 +88,7 @@ function initState(): State {
|
||||||
transactions[id] = {
|
transactions[id] = {
|
||||||
id,
|
id,
|
||||||
status: Status.Checking,
|
status: Status.Checking,
|
||||||
recent: true,
|
source: Source.Url,
|
||||||
signature
|
signature
|
||||||
};
|
};
|
||||||
return transactions;
|
return transactions;
|
||||||
|
@ -72,9 +109,8 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
||||||
|
|
||||||
// Check transaction statuses on startup and whenever network updates
|
// Check transaction statuses on startup and whenever network updates
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const connection = new Connection(url);
|
|
||||||
Object.values(state.transactions).forEach(tx => {
|
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
|
}, [status, url]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
@ -89,23 +125,24 @@ export function TransactionsProvider({ children }: TransactionsProviderProps) {
|
||||||
|
|
||||||
export async function checkTransactionStatus(
|
export async function checkTransactionStatus(
|
||||||
dispatch: Dispatch,
|
dispatch: Dispatch,
|
||||||
transaction: Transaction,
|
id: number,
|
||||||
connection: Connection
|
signature: TransactionSignature,
|
||||||
|
url: string
|
||||||
) {
|
) {
|
||||||
const id = transaction.id;
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
type: ActionType.UpdateStatus,
|
||||||
status: Status.Checking,
|
status: Status.Checking,
|
||||||
id
|
id
|
||||||
});
|
});
|
||||||
|
|
||||||
let status;
|
let status;
|
||||||
try {
|
try {
|
||||||
const signatureStatus = await connection.getSignatureStatus(
|
const signatureStatus = await new Connection(url).getSignatureStatus(
|
||||||
transaction.signature
|
signature
|
||||||
);
|
);
|
||||||
|
|
||||||
if (signatureStatus === null) {
|
if (signatureStatus === null) {
|
||||||
status = Status.Pending;
|
status = Status.Missing;
|
||||||
} else if ("Ok" in signatureStatus) {
|
} else if ("Ok" in signatureStatus) {
|
||||||
status = Status.Success;
|
status = Status.Success;
|
||||||
} else {
|
} else {
|
||||||
|
@ -115,7 +152,7 @@ export async function checkTransactionStatus(
|
||||||
console.error("Failed to check transaction status", error);
|
console.error("Failed to check transaction status", error);
|
||||||
status = Status.CheckFailed;
|
status = Status.CheckFailed;
|
||||||
}
|
}
|
||||||
dispatch({ status, id });
|
dispatch({ type: ActionType.UpdateStatus, status, id });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTransactions() {
|
export function useTransactions() {
|
||||||
|
@ -125,7 +162,12 @@ export function useTransactions() {
|
||||||
`useTransactions must be used within a TransactionsProvider`
|
`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() {
|
export function useTransactionsDispatch() {
|
||||||
|
|
|
@ -32,4 +32,12 @@ code {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function assertUnreachable(x: never): never {
|
||||||
|
throw new Error("Unreachable!");
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue