Skip to content

Commit

Permalink
Merge pull request #16 from polkadot-api/notifications
Browse files Browse the repository at this point in the history
show transaction notifications
  • Loading branch information
voliva authored Aug 20, 2024
2 parents 376f944 + d6cfe32 commit 0b9c547
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 69 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"react-dom": "^18.3.1",
"react-portal": "^4.2.2",
"react-router-dom": "^6.26.1",
"react-toastify": "^10.0.5",
"rxjs": "^7.8.1",
"tailwind-merge": "^2.5.2"
},
Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 81 additions & 55 deletions src/actions/send/Submit.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,97 @@
import * as Progress from "@radix-ui/react-progress"
import { useStateObservable } from "@react-rxjs/core"
import { useEffect, useState } from "react"
import { senderChainId$, submitTransfer$, transferStatus$ } from "./send"
import { state } from "@react-rxjs/core"
import { allChains, ChainId } from "@/api"
import { of, merge } from "rxjs"
import { state, useStateObservable, withDefault } from "@react-rxjs/core"
import { map, of, switchMap, withLatestFrom } from "rxjs"
import {
senderChainId$,
submitTransfer$,
TransactionStatus,
transferStatus$,
} from "./send"
import { allChains } from "@/api"
import { twMerge } from "tailwind-merge"

transferStatus$.subscribe()

const finalizedBlock$ = state(
(chainId: ChainId | "") =>
chainId === "" ? of(null) : allChains[chainId].client.finalizedBlock$,
senderChainId$.pipe(
switchMap((chain) =>
chain ? allChains[chain].client.finalizedBlock$ : of(null),
),
),
null,
)

const subscriptions = state(merge(transferStatus$)).subscribe()
const progress$ = transferStatus$.pipeState(
withLatestFrom(finalizedBlock$),
switchMap(([v, finalized]) => {
if (!v) return [null]

export default function Submit() {
const txStatus = useStateObservable(transferStatus$)
const selectedChain = useStateObservable(senderChainId$)!
const finalizedBlock = useStateObservable(finalizedBlock$(selectedChain))
if (
v.status === TransactionStatus.BestBlock &&
"number" in v &&
v.number &&
finalized
) {
const start = finalized.number
const end = v.number
return finalizedBlock$.pipe(
map((finalized) => ({
ok: v.ok,
value: v.status,
subProgress: {
value: finalized!.number - start,
total: end - start,
},
})),
)
}

const [isSubmitting, setSubmitting] = useState(false)
const [isTransacting, setIsTransacting] = useState(false)
const [statusLabel, setStatusLabel] = useState("")
return [
{
ok: v.ok,
value: v.status,
},
]
}),
withDefault(null),
)

const [progress, setProgress] = useState(2)
const transactionStatusLabel: Record<TransactionStatus, string> = {
[TransactionStatus.Signing]: "Signing",
[TransactionStatus.Broadcasted]:
"Broadcasting complete. Sending to best blocks",
[TransactionStatus.BestBlock]: "In best block state: ",
[TransactionStatus.Finalized]: "Transaction completed successfully!",
}

useEffect(() => {
setProgress(5)
}, [isSubmitting])
export default function Submit() {
const selectedChain = useStateObservable(senderChainId$)

useEffect(() => {
switch (txStatus?.type) {
case "signed": {
setProgress(25)
setIsTransacting(true)
setStatusLabel("Transaction Signed successfully. Broadcasting...")
break
}
case "broadcasted":
setProgress(50)
setStatusLabel("Broadcasting complete. Sending to best blocks")
break
case "txBestBlocksState":
setProgress(75)
setStatusLabel("In best blocks state: ")
// set micro progress per block
break
case "finalized":
setProgress(100)
setStatusLabel("Transaction completed successfully!")
const txProgress = useStateObservable(progress$)
const isTransacting =
txProgress && txProgress.value > TransactionStatus.Signing
const isSigning = !!txProgress && !isTransacting
const progress =
(txProgress?.value ?? 0) +
(txProgress && "subProgress" in txProgress
? (50 * txProgress.subProgress.value) / txProgress.subProgress.total
: 0)

// setTimeout(() => )
break
}
}, [txStatus])
if (!selectedChain) return null

return (
<div className="mb-5">
{isTransacting ? (
<>
<div className="mb-4 text-pink font-semibold flex flex-col">
<span>{statusLabel}</span>
{txStatus?.type === "txBestBlocksState" && txStatus.found === true
? `${finalizedBlock?.number}/${txStatus.block.number}`
<div
className={twMerge(
"mb-4 text-pink font-semibold flex flex-col",
txProgress.ok ? "" : "text-orange-500",
)}
>
<span>{transactionStatusLabel[txProgress.value]}</span>
{"subProgress" in txProgress
? `${txProgress.subProgress.value}/${txProgress.subProgress.total}`
: null}
</div>
<Progress.Root
Expand All @@ -81,14 +110,11 @@ export default function Submit() {
) : (
<>
<button
className={`rounded mb-10 bg-pink p-2 text-white w-40 ${!selectedChain || isSubmitting ? "opacity-80" : ""}`}
disabled={!selectedChain || isSubmitting}
onClick={() => {
submitTransfer$()
setSubmitting(true)
}}
className={`rounded mb-10 bg-pink p-2 text-white w-40 ${isSigning ? "opacity-80" : ""}`}
disabled={isSigning}
onClick={submitTransfer$}
>
{isSubmitting ? "Sign transaction" : "Send Transaction"}
{isSigning ? "Sign transaction" : "Send Transaction"}
</button>
</>
)}
Expand Down
70 changes: 63 additions & 7 deletions src/actions/send/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ import { parseCurrency } from "@/utils/currency"
import { state } from "@react-rxjs/core"
import { createSignal } from "@react-rxjs/utils"
import {
catchError,
combineLatest,
defer,
filter,
map,
materialize,
of,
scan,
startWith,
switchMap,
tap,
withLatestFrom,
} from "rxjs"
import { findRoute, predefinedTransfers } from "./transfers"
import { toast } from "react-toastify"

const PATTERN = "/send/:chain/:account"

Expand Down Expand Up @@ -213,10 +215,7 @@ export const feeEstimation$ = state(
.tx(recipient, transferAmount)
.getEstimatedFees(selectedAccount.address),
),
).pipe(
tap((v) => console.log(v)),
map((v) => v.reduce((a, b) => a + b)),
)
).pipe(map((v) => v.reduce((a, b) => a + b, 0n)))
: [null]
},
),
Expand Down Expand Up @@ -252,15 +251,72 @@ export const tx$ = state(
),
)

export enum TransactionStatus {
Signing = 5,
Broadcasted = 25,
BestBlock = 50,
Finalized = 100,
}

const errorToast = (error: string) =>
toast(error, {
type: "error",
})

const successToast = (message: string) =>
toast(message, {
type: "success",
})

export const transferStatus$ = state(
onSubmitted$.pipe(
withLatestFrom(tx$, selectedAccount$),
switchMap(([, tx, selectedAccount]) => {
if (!tx || !selectedAccount) return []

return tx.signSubmitAndWatch(selectedAccount.polkadotSigner)
return tx.signSubmitAndWatch(selectedAccount.polkadotSigner).pipe(
map((v) => {
switch (v.type) {
case "signed":
case "broadcasted":
return {
ok: true,
status: TransactionStatus.Broadcasted,
}
case "txBestBlocksState":
return {
ok: v.found && v.ok,
status: TransactionStatus.BestBlock,
number: v.found ? v.block.number : null,
}
case "finalized":
if (!v.ok) {
console.log("dispatchError", v.dispatchError)
const error =
v.dispatchError.type === "Module"
? JSON.stringify(v.dispatchError.value)
: v.dispatchError.type
errorToast("Transaction didn't succeed: " + error)
return null
}
successToast("Transaction succeeded 🎉")
return {
ok: true,
status: TransactionStatus.Finalized,
}
}
}),
catchError((err) => {
errorToast("Transaction failed: " + (err.message ?? "Unknown"))

return of(null)
}),
startWith({
ok: true,
status: TransactionStatus.Signing,
}),
)
}),
tap((status) => console.log("Tx status: ", status)),
),
null,
)
7 changes: 0 additions & 7 deletions src/actions/void.tsx

This file was deleted.

3 changes: 3 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { createRoot } from "react-dom/client"
import App from "./App.tsx"
import "./index.css"
import { Router } from "./router"
import { ToastContainer } from "react-toastify"
import "react-toastify/dist/ReactToastify.css"

createRoot(document.getElementById("root")!).render(
<StrictMode>
<Router>
<App />
</Router>
<ToastContainer />
</StrictMode>,
)

0 comments on commit 0b9c547

Please sign in to comment.