diff --git a/src/App.tsx b/src/App.tsx index e52724e43e5d96..c2c9b4dbe2a023 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,28 +1,40 @@ import React from "react"; import { NetworkProvider } from "./providers/network"; -import NetworkStatusButton from "./components/networkStatusButton"; +import { TransactionsProvider } from "./providers/transactions"; +import NetworkStatusButton from "./components/NetworkStatusButton"; +import TransactionsCard from "./components/TransactionsCard"; function App() { return ( -
-
-
-
-
-
-
Beta
-

Solana Explorer

-
-
- + +
+
+
+
+
+
+
Beta
+

Solana Explorer

+
+
- +
+ +
+
+
+ + + +
+
+
-
+ ); } diff --git a/src/components/networkStatusButton.tsx b/src/components/NetworkStatusButton.tsx similarity index 72% rename from src/components/networkStatusButton.tsx rename to src/components/NetworkStatusButton.tsx index 3181b1ba0eb6fe..34869ad391c942 100644 --- a/src/components/networkStatusButton.tsx +++ b/src/components/NetworkStatusButton.tsx @@ -6,22 +6,22 @@ function NetworkStatusButton() { switch (status) { case NetworkStatus.Connected: - return {url}; + return {url}; case NetworkStatus.Connecting: return ( - + {"Connecting "} - + ); case NetworkStatus.Failure: - return Disconnected; + return Disconnected; } } diff --git a/src/components/TransactionsCard.tsx b/src/components/TransactionsCard.tsx new file mode 100644 index 00000000000000..7223d653f1f6b4 --- /dev/null +++ b/src/components/TransactionsCard.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import { + useTransactions, + Transaction, + Status +} from "../providers/transactions"; + +function TransactionsCard() { + const { transactions } = useTransactions(); + + return ( +
+ {renderHeader()} + +
+ + + + + + + + + + + {Object.values(transactions).map(transaction => + renderTransactionRow(transaction) + )} + +
StatusSignatureConfirmationsSlot Number
+
+
+ ); +} + +const renderHeader = () => { + return ( +
+
+
+

Transactions

+
+
+
+ ); +}; + +const renderTransactionRow = (transaction: Transaction) => { + let statusText; + let statusClass; + switch (transaction.status) { + case Status.CheckFailed: + statusClass = "dark"; + statusText = "Network Error"; + break; + case Status.Checking: + statusClass = "info"; + statusText = "Checking"; + break; + case Status.Success: + statusClass = "success"; + statusText = "Success"; + break; + case Status.Failure: + statusClass = "danger"; + statusText = "Failed"; + break; + case Status.Pending: + statusClass = "warning"; + statusText = "Pending"; + break; + } + + return ( + + + {statusText} + + + {transaction.signature} + + TODO + TODO + + ); +}; + +export default TransactionsCard; diff --git a/src/providers/transactions.tsx b/src/providers/transactions.tsx new file mode 100644 index 00000000000000..4aca742259128a --- /dev/null +++ b/src/providers/transactions.tsx @@ -0,0 +1,139 @@ +import React from "react"; +import { TransactionSignature, Connection } from "@solana/web3.js"; +import { findGetParameter } from "../utils"; +import { useNetwork } from "../providers/network"; + +export enum Status { + Checking, + CheckFailed, + Success, + Failure, + Pending +} + +export interface Transaction { + id: number; + status: Status; + recent: boolean; + signature: TransactionSignature; +} + +type Transactions = { [id: number]: Transaction }; +interface State { + idCounter: number; + transactions: Transactions; +} + +interface UpdateStatus { + id: number; + status: Status; +} + +type Action = UpdateStatus; +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 }; + } + return state; +} + +function initState(): State { + let idCounter = 0; + const signatures = findGetParameter("txs")?.split(",") || []; + const transactions = signatures.reduce( + (transactions: Transactions, signature) => { + const id = ++idCounter; + transactions[id] = { + id, + status: Status.Checking, + recent: true, + signature + }; + return transactions; + }, + {} + ); + return { idCounter, transactions }; +} + +const StateContext = React.createContext(undefined); +const DispatchContext = React.createContext(undefined); + +type TransactionsProviderProps = { children: React.ReactNode }; +export function TransactionsProvider({ children }: TransactionsProviderProps) { + const [state, dispatch] = React.useReducer(reducer, undefined, initState); + + const { status, url } = useNetwork(); + + // 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); + }); + }, [status, url]); // eslint-disable-line react-hooks/exhaustive-deps + + return ( + + + {children} + + + ); +} + +export async function checkTransactionStatus( + dispatch: Dispatch, + transaction: Transaction, + connection: Connection +) { + const id = transaction.id; + dispatch({ + status: Status.Checking, + id + }); + + let status; + try { + const signatureStatus = await connection.getSignatureStatus( + transaction.signature + ); + + if (signatureStatus === null) { + status = Status.Pending; + } else if ("Ok" in signatureStatus) { + status = Status.Success; + } else { + status = Status.Failure; + } + } catch (error) { + console.error("Failed to check transaction status", error); + status = Status.CheckFailed; + } + dispatch({ status, id }); +} + +export function useTransactions() { + const context = React.useContext(StateContext); + if (!context) { + throw new Error( + `useTransactions must be used within a TransactionsProvider` + ); + } + return context; +} + +export function useTransactionsDispatch() { + const context = React.useContext(DispatchContext); + if (!context) { + throw new Error( + `useTransactionsDispatch must be used within a TransactionsProvider` + ); + } + return context; +} diff --git a/src/scss/_solana.scss b/src/scss/_solana.scss index 740082f2ebc2b5..20ff90948ffa00 100644 --- a/src/scss/_solana.scss +++ b/src/scss/_solana.scss @@ -2,3 +2,10 @@ // solana.scss // Use this to write your custom SCSS // + +code { + padding: 0.33rem; + border-radius: $border-radius; + background-color: $gray-200; + color: $black; +}