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

View File

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

View File

@ -3,18 +3,17 @@
"description": "Privacy preserving and transparent payments",
"author": "GNU Taler Developers",
"version": "0.9.3.13",
"id": "gnu-taler-wallet-web-spa-development",
"version_name": "0.9.3-dev.13",
"icons": {
"16": "static/img/taler-logo-16.png",
"19": "static/img/taler-logo-19.png",
"32": "static/img/taler-logo-32.png",
"38": "static/img/taler-logo-38.png",
"48": "static/img/taler-logo-48.png",
"64": "static/img/taler-logo-64.png",
"128": "static/img/taler-logo-128.png",
"256": "static/img/taler-logo-256.png",
"512": "static/img/taler-logo-512.png"
},
"icons": [{
"src": "static/img/taler-logo-48.png",
"sizes": "48x48"
},{
"src": "static/img/taler-logo-128.png",
"sizes": "128x128"
}],
"display": "minimal-ui",
"start_url": "http://localhost:8080/app/index.html",
"manifest_version": 3,
"minimum_chrome_version": "88",
"permissions": [
@ -75,8 +74,5 @@
},
"default_title": "GNU Taler Wallet",
"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";
export default {
title: "balance",
title: "history",
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(
TestedComponent,
{

View File

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

View File

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