tx state ui

This commit is contained in:
Sebastian 2023-06-01 14:26:28 -03:00
parent 699a7b453b
commit 4f726b73e6
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
11 changed files with 676 additions and 69 deletions

View File

@ -39,7 +39,7 @@ import {
} from "./styled/index.js"; } from "./styled/index.js";
import { Time } from "./Time.js"; import { Time } from "./Time.js";
export function TransactionItem(props: { tx: Transaction }): VNode { export function HistoryItem(props: { tx: Transaction }): VNode {
const tx = props.tx; const tx = props.tx;
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
/** /**
@ -48,14 +48,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
switch (tx.type) { switch (tx.type) {
case TransactionType.Withdrawal: case TransactionType.Withdrawal:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={new URL(tx.exchangeBaseUrl).hostname} title={new URL(tx.exchangeBaseUrl).hostname}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"W"} iconPath={"W"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? tx.withdrawalDetails.type === ? tx.withdrawalDetails.type ===
WithdrawalType.TalerBankIntegrationApi WithdrawalType.TalerBankIntegrationApi
@ -71,14 +72,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.InternalWithdrawal: case TransactionType.InternalWithdrawal:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={new URL(tx.exchangeBaseUrl).hostname} title={new URL(tx.exchangeBaseUrl).hostname}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"I"} iconPath={"I"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? tx.withdrawalDetails.type === ? tx.withdrawalDetails.type ===
WithdrawalType.TalerBankIntegrationApi WithdrawalType.TalerBankIntegrationApi
@ -94,7 +96,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.Payment: case TransactionType.Payment:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"debit"} debitCreditIndicator={"debit"}
@ -102,7 +104,8 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
subtitle={tx.info.summary} subtitle={tx.info.summary}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"P"} iconPath={"P"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Payment in progress` ? i18n.str`Payment in progress`
: undefined : undefined
@ -111,7 +114,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.Refund: case TransactionType.Refund:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
@ -123,7 +126,8 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
} //FIXME: DD37 wallet-core is not returning this value } //FIXME: DD37 wallet-core is not returning this value
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"R"} iconPath={"R"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Executing refund...` ? i18n.str`Executing refund...`
: undefined : undefined
@ -132,14 +136,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.Tip: case TransactionType.Tip:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={new URL(tx.merchantBaseUrl).hostname} title={new URL(tx.merchantBaseUrl).hostname}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"T"} iconPath={"T"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Grabbing the tipping...` ? i18n.str`Grabbing the tipping...`
: undefined : undefined
@ -148,14 +153,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.Refresh: case TransactionType.Refresh:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={"Refresh"} title={"Refresh"}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"R"} iconPath={"R"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Refreshing coins...` ? i18n.str`Refreshing coins...`
: undefined : undefined
@ -164,14 +170,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.Deposit: case TransactionType.Deposit:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"debit"} debitCreditIndicator={"debit"}
title={tx.targetPaytoUri} title={tx.targetPaytoUri}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"D"} iconPath={"D"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Deposit in progress` ? i18n.str`Deposit in progress`
: undefined : undefined
@ -180,14 +187,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.PeerPullCredit: case TransactionType.PeerPullCredit:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={tx.info.summary || "Invoice"} title={tx.info.summary || "Invoice"}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"I"} iconPath={"I"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Waiting to be paid` ? i18n.str`Waiting to be paid`
: undefined : undefined
@ -196,14 +204,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.PeerPullDebit: case TransactionType.PeerPullDebit:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"debit"} debitCreditIndicator={"debit"}
title={tx.info.summary || "Invoice"} title={tx.info.summary || "Invoice"}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"I"} iconPath={"I"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Payment in progress` ? i18n.str`Payment in progress`
: undefined : undefined
@ -212,14 +221,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.PeerPushCredit: case TransactionType.PeerPushCredit:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"credit"} debitCreditIndicator={"credit"}
title={tx.info.summary || "Transfer"} title={tx.info.summary || "Transfer"}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"T"} iconPath={"T"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Receiving the transfer` ? i18n.str`Receiving the transfer`
: undefined : undefined
@ -228,14 +238,15 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
); );
case TransactionType.PeerPushDebit: case TransactionType.PeerPushDebit:
return ( return (
<TransactionLayout <Layout
id={tx.transactionId} id={tx.transactionId}
amount={tx.amountEffective} amount={tx.amountEffective}
debitCreditIndicator={"debit"} debitCreditIndicator={"debit"}
title={tx.info.summary || "Transfer"} title={tx.info.summary || "Transfer"}
timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)} timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
iconPath={"T"} iconPath={"T"}
pending={ currentState={tx.txState.major}
description={
tx.txState.major === TransactionMajorState.Pending tx.txState.major === TransactionMajorState.Pending
? i18n.str`Waiting to be received` ? i18n.str`Waiting to be received`
: undefined : undefined
@ -248,13 +259,22 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
} }
} }
function TransactionLayout(props: TransactionLayoutProps): VNode { function Layout(props: LayoutProps): VNode {
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
return ( return (
<HistoryRow <HistoryRow
href={Pages.balanceTransaction({ tid: props.id })} href={Pages.balanceTransaction({ tid: props.id })}
style={{ style={{
backgroundColor: props.pending ? "lightcyan" : "inherit", backgroundColor:
props.currentState === TransactionMajorState.Pending ||
props.currentState === TransactionMajorState.Dialog
? "lightcyan"
: props.currentState === TransactionMajorState.Failed
? "#ff000040"
: props.currentState === TransactionMajorState.Aborted ||
props.currentState === TransactionMajorState.Aborting
? "#00000010"
: "inherit",
alignItems: "center", alignItems: "center",
}} }}
> >
@ -276,9 +296,9 @@ function TransactionLayout(props: TransactionLayoutProps): VNode {
</div> </div>
)} )}
</LargeText> </LargeText>
{props.pending && ( {props.description && (
<LightText style={{ marginTop: 5, marginBottom: 5 }}> <LightText style={{ marginTop: 5, marginBottom: 5 }}>
<i18n.Translate>{props.pending}</i18n.Translate> <i18n.Translate>{props.description}</i18n.Translate>
</LightText> </LightText>
)} )}
<SmallLightText style={{ marginTop: 5 }}> <SmallLightText style={{ marginTop: 5 }}>
@ -286,7 +306,7 @@ function TransactionLayout(props: TransactionLayoutProps): VNode {
</SmallLightText> </SmallLightText>
</Column> </Column>
<TransactionAmount <TransactionAmount
pending={props.pending !== undefined} currentState={props.currentState}
amount={Amounts.parseOrThrow(props.amount)} amount={Amounts.parseOrThrow(props.amount)}
debitCreditIndicator={props.debitCreditIndicator} debitCreditIndicator={props.debitCreditIndicator}
/> />
@ -294,7 +314,7 @@ function TransactionLayout(props: TransactionLayoutProps): VNode {
); );
} }
interface TransactionLayoutProps { interface LayoutProps {
debitCreditIndicator: "debit" | "credit" | "unknown"; debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountString | "unknown"; amount: AmountString | "unknown";
timestamp: AbsoluteTime; timestamp: AbsoluteTime;
@ -302,13 +322,14 @@ interface TransactionLayoutProps {
subtitle?: string; subtitle?: string;
id: string; id: string;
iconPath: string; iconPath: string;
pending?: string; currentState: TransactionMajorState;
description?: string;
} }
interface TransactionAmountProps { interface TransactionAmountProps {
debitCreditIndicator: "debit" | "credit" | "unknown"; debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountJson; amount: AmountJson;
pending: boolean; currentState: TransactionMajorState;
} }
function TransactionAmount(props: TransactionAmountProps): VNode { function TransactionAmount(props: TransactionAmountProps): VNode {
@ -328,24 +349,43 @@ function TransactionAmount(props: TransactionAmountProps): VNode {
<Column <Column
style={{ style={{
textAlign: "center", textAlign: "center",
color: props.pending color:
? "gray" props.currentState !== TransactionMajorState.Done
: sign === "+" ? "gray"
? "darkgreen" : sign === "+"
: sign === "-" ? "darkgreen"
? "darkred" : sign === "-"
: undefined, ? "darkred"
: undefined,
}} }}
> >
<ExtraLargeText> <ExtraLargeText>
{sign} {sign}
{Amounts.stringifyValue(props.amount, 2)} {Amounts.stringifyValue(props.amount, 2)}
</ExtraLargeText> </ExtraLargeText>
{props.pending && ( {props.currentState === TransactionMajorState.Aborted ? (
<div> <div
<i18n.Translate>PENDING</i18n.Translate> style={{
color: "black",
border: "1px black solid",
borderRadius: 8,
padding: 4,
}}
>
<i18n.Translate>ABORTED</i18n.Translate>
</div> </div>
)} ) : props.currentState === TransactionMajorState.Failed ? (
<div
style={{
color: "red",
border: "1px darkred solid",
borderRadius: 8,
padding: 4,
}}
>
<i18n.Translate>FAILED</i18n.Translate>
</div>
) : undefined}
</Column> </Column>
); );
} }

View File

@ -1,7 +1,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="manifest" href="manifest.json" /> <link rel="manifest" href="./manifest.json" />
<style> <style>
.overlay { .overlay {
position: absolute; position: absolute;
@ -54,8 +54,9 @@ justify-content: center;
width="1000" width="1000"
height="100%" height="100%"
> >
<input id="page-url" type="text" /> </iframe>
<button onclick="openPage()">open</button> <!-- <input id="page-url" type="text" />
<button onclick="openPage()">open</button> -->
<!-- <a <!-- <a
href='javascript:void(window.frames["other"].location = "http://bank.taler:5882")' href='javascript:void(window.frames["other"].location = "http://bank.taler:5882")'
>open local bank</a >open local bank</a
@ -77,17 +78,13 @@ justify-content: center;
src="about:blank" src="about:blank"
width="500" width="500"
height="325" height="325"
> >
</iframe> </iframe>
</div> </div>
<hr />
</iframe>
<!-- <hr /> <!-- <hr />
<iframe src="tests.html" name="wallet" width="800" height="100%"> </iframe> --> <iframe src="tests.html" name="wallet" width="800" height="100%"> </iframe> -->
<!-- <hr /> <!-- <hr />
<iframe src="stories.html" name="wallet" width="800" height="100%"> --> <iframe src="stories.html" name="wallet" width="800" height="100%"> -->
</iframe>
<hr />
<script type="module" src="background.dev.js"></script> <script type="module" src="background.dev.js"></script>
</body> </body>
</html> </html>

View File

@ -3,18 +3,17 @@
"description": "Privacy preserving and transparent payments", "description": "Privacy preserving and transparent payments",
"author": "GNU Taler Developers", "author": "GNU Taler Developers",
"version": "0.9.3.13", "version": "0.9.3.13",
"id": "gnu-taler-wallet-web-spa-development",
"version_name": "0.9.3-dev.13", "version_name": "0.9.3-dev.13",
"icons": { "icons": [{
"16": "static/img/taler-logo-16.png", "src": "static/img/taler-logo-48.png",
"19": "static/img/taler-logo-19.png", "sizes": "48x48"
"32": "static/img/taler-logo-32.png", },{
"38": "static/img/taler-logo-38.png", "src": "static/img/taler-logo-128.png",
"48": "static/img/taler-logo-48.png", "sizes": "128x128"
"64": "static/img/taler-logo-64.png", }],
"128": "static/img/taler-logo-128.png", "display": "minimal-ui",
"256": "static/img/taler-logo-256.png", "start_url": "http://localhost:8080/app/index.html",
"512": "static/img/taler-logo-512.png"
},
"manifest_version": 3, "manifest_version": 3,
"minimum_chrome_version": "88", "minimum_chrome_version": "88",
"permissions": [ "permissions": [
@ -75,8 +74,5 @@
}, },
"default_title": "GNU Taler Wallet", "default_title": "GNU Taler Wallet",
"default_popup": "static/popup.html" "default_popup": "static/popup.html"
},
"background": {
"service_worker": "service_worker.js"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -43,7 +43,7 @@ import { HistoryView as TestedComponent } from "./History.js";
import * as tests from "@gnu-taler/web-util/testing"; import * as tests from "@gnu-taler/web-util/testing";
export default { export default {
title: "balance", title: "history",
component: TestedComponent, component: TestedComponent,
}; };
@ -291,6 +291,100 @@ export const SomeTransactions = tests.createExample(TestedComponent, {
], ],
}); });
export const SomeTransactionsInDifferentStates = tests.createExample(
TestedComponent,
{
transactions: [
exampleData.withdraw,
{
...exampleData.withdraw,
exchangeBaseUrl: "https://aborted/withdrawal",
txState: {
major: TransactionMajorState.Aborted,
},
},
{
...exampleData.withdraw,
exchangeBaseUrl: "https://pending/withdrawal",
txState: {
major: TransactionMajorState.Pending,
},
},
{
...exampleData.withdraw,
exchangeBaseUrl: "https://failed/withdrawal",
txState: {
major: TransactionMajorState.Failed,
},
},
{
...exampleData.payment,
info: {
...exampleData.payment.info,
summary: "normal payment",
},
},
{
...exampleData.payment,
info: {
...exampleData.payment.info,
summary: "aborting in progress",
},
txState: {
major: TransactionMajorState.Aborting,
},
},
{
...exampleData.payment,
info: {
...exampleData.payment.info,
summary: "aborted payment",
},
txState: {
major: TransactionMajorState.Aborted,
},
},
{
...exampleData.payment,
info: {
...exampleData.payment.info,
summary: "pending payment",
},
txState: {
major: TransactionMajorState.Pending,
},
},
{
...exampleData.payment,
info: {
...exampleData.payment.info,
summary: "failed payment",
},
txState: {
major: TransactionMajorState.Failed,
},
},
exampleData.refund,
exampleData.tip,
exampleData.deposit,
],
balances: [
{
available: "USD:10",
pendingIncoming: "USD:0",
pendingOutgoing: "USD:0",
hasPendingTransactions: false,
requiresUserInput: false,
scopeInfo: {
currency: "Ásd",
type: ScopeType.Auditor,
url: "",
},
},
],
},
);
export const SomeTransactionsWithTwoCurrencies = tests.createExample( export const SomeTransactionsWithTwoCurrencies = tests.createExample(
TestedComponent, TestedComponent,
{ {

View File

@ -33,7 +33,7 @@ import {
NiceSelect, NiceSelect,
} from "../components/styled/index.js"; } from "../components/styled/index.js";
import { Time } from "../components/Time.js"; import { Time } from "../components/Time.js";
import { TransactionItem } from "../components/TransactionItem.js"; import { HistoryItem } from "../components/HistoryItem.js";
import { alertFromError, useAlertContext } from "../context/alert.js"; import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { useTranslationContext } from "@gnu-taler/web-util/browser";
@ -269,7 +269,7 @@ export function HistoryView({
/> />
</DateSeparator> </DateSeparator>
{byDate[d].map((tx, i) => ( {byDate[d].map((tx, i) => (
<TransactionItem key={i} tx={tx} /> <HistoryItem key={i} tx={tx} />
))} ))}
</Fragment> </Fragment>
); );

View File

@ -52,6 +52,7 @@ import { QR } from "../components/QR.js";
import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js"; import { ShowFullContractTermPopup } from "../components/ShowFullContractTermPopup.js";
import { import {
CenteredDialog, CenteredDialog,
ErrorBox,
InfoBox, InfoBox,
ListOfProducts, ListOfProducts,
Overlay, Overlay,
@ -128,7 +129,7 @@ export function TransactionPage({ tid, goToWalletHistory }: Props): VNode {
goToWalletHistory(currency); goToWalletHistory(currency);
}} }}
onResume={async () => { onResume={async () => {
await api.wallet.call(WalletApiOperation.SuspendTransaction, { await api.wallet.call(WalletApiOperation.ResumeTransaction, {
transactionId, transactionId,
}); });
goToWalletHistory(currency); goToWalletHistory(currency);
@ -239,6 +240,7 @@ function TransactionTemplate({
transaction.txState.major === TransactionMajorState.Aborting; transaction.txState.major === TransactionMajorState.Aborting;
const showRetry = const showRetry =
!isFinalState &&
transaction.txState.major !== TransactionMajorState.Pending && transaction.txState.major !== TransactionMajorState.Pending &&
transaction.txState.major !== TransactionMajorState.Aborting; transaction.txState.major !== TransactionMajorState.Aborting;
@ -289,6 +291,16 @@ function TransactionTemplate({
<i18n.Translate>This transaction is not completed</i18n.Translate> <i18n.Translate>This transaction is not completed</i18n.Translate>
</WarningBox> </WarningBox>
)} )}
{transaction.txState.major === TransactionMajorState.Aborted && (
<InfoBox>
<i18n.Translate>This transaction was aborted</i18n.Translate>
</InfoBox>
)}
{transaction.txState.major === TransactionMajorState.Failed && (
<ErrorBox>
<i18n.Translate>This transaction failed</i18n.Translate>
</ErrorBox>
)}
{confirmBeforeForget ? ( {confirmBeforeForget ? (
<Overlay> <Overlay>
<CenteredDialog> <CenteredDialog>