towards a nicer transaction history

This commit is contained in:
Florian Dold 2020-11-18 17:33:02 +01:00
parent d6409f185d
commit 9cd1062f1b
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
10 changed files with 179 additions and 12 deletions

View File

@ -332,6 +332,7 @@ export async function getTransactions(
TransactionType.Tip,
tipRecord.walletTipId,
),
merchantBaseUrl: tipRecord.merchantBaseUrl,
error: tipRecord.lastError,
});
});

View File

@ -288,6 +288,8 @@ interface TransactionTip extends TransactionCommon {
// Amount will be (or was) added to the wallet's balance after fees and refreshing
amountEffective: AmountString;
merchantBaseUrl: string;
}
// A transaction shown for refreshes that are not associated to other transactions

View File

@ -36,6 +36,8 @@ import {
TransactionsResponse,
Transaction,
TransactionType,
AmountString,
Timestamp,
} from "taler-wallet-core";
import { abbrev, renderAmount, PageLink } from "../renderHtml";
@ -301,19 +303,161 @@ class WalletBalanceView extends React.Component<any, any> {
}
}
function Icon({ l }: { l: string }): JSX.Element {
return <div className={"icon"}>{l}</div>;
interface TransactionAmountProps {
debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountString | "unknown";
pending: boolean;
}
function formatAndCapitalize(text: string): string {
text = text.replace("-", " ");
text = text.replace(/^./, text[0].toUpperCase());
return text;
function TransactionAmount(props: TransactionAmountProps): JSX.Element {
const [currency, amount] = props.amount.split(":");
let sign: string;
switch (props.debitCreditIndicator) {
case "credit":
sign = "+";
break;
case "debit":
sign = "-";
break;
case "unknown":
sign = "";
}
const style: React.CSSProperties = {
marginLeft: "auto",
display: "flex",
flexDirection: "column",
alignItems: "center",
alignSelf: "center"
};
if (props.pending) {
style.color = "gray";
}
return (
<div style={{ ...style }}>
<div style={{ fontSize: "x-large" }}>
{sign}
{amount}
</div>
<div>{currency}</div>
</div>
);
}
interface TransactionLayoutProps {
debitCreditIndicator: "debit" | "credit" | "unknown";
amount: AmountString | "unknown";
timestamp: Timestamp;
title: string;
subtitle: string;
iconPath: string;
pending: boolean;
}
function TransactionLayout(props: TransactionLayoutProps): JSX.Element {
const date = new Date(props.timestamp.t_ms);
const dateStr = date.toLocaleString([], {
dateStyle: "medium",
timeStyle: "short",
} as any);
return (
<div
style={{
display: "flex",
flexDirection: "row",
border: "1px solid gray",
borderRadius: "0.5em",
margin: "0.5em 0",
justifyContent: "space-between",
padding: "0.5em",
}}
>
<img src={props.iconPath} />
<div
style={{ display: "flex", flexDirection: "column", marginLeft: "1em" }}
>
<div style={{ fontSize: "small", color: "gray" }}>{dateStr}</div>
<div style={{ fontVariant: "small-caps", fontSize: "x-large" }}>
<span>{props.title}</span>
{props.pending ? (
<span style={{ color: "darkblue" }}> (Pending)</span>
) : null}
</div>
<div>{props.subtitle}</div>
</div>
<TransactionAmount
pending={props.pending}
amount={props.amount}
debitCreditIndicator={props.debitCreditIndicator}
/>
</div>
);
}
function TransactionItem(props: { tx: Transaction }): JSX.Element {
const tx = props.tx;
return <pre>{JSON.stringify(tx)}</pre>
switch (tx.type) {
case TransactionType.Withdrawal:
return (
<TransactionLayout
amount={tx.amountEffective}
debitCreditIndicator={"credit"}
title="Withdrawal"
subtitle={`via ${tx.exchangeBaseUrl}`}
timestamp={tx.timestamp}
iconPath="/static/img/ri-bank-line.svg"
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Payment:
return (
<TransactionLayout
amount={tx.amountEffective}
debitCreditIndicator={"debit"}
title="Payment"
subtitle={tx.info.summary}
timestamp={tx.timestamp}
iconPath="/static/img/ri-shopping-cart-line.svg"
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Refund:
return (
<TransactionLayout
amount={tx.amountEffective}
debitCreditIndicator={"credit"}
title="Refund"
subtitle={tx.info.summary}
timestamp={tx.timestamp}
iconPath="/static/img/ri-refund-2-line.svg"
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Tip:
return (
<TransactionLayout
amount={tx.amountEffective}
debitCreditIndicator={"credit"}
title="Tip"
subtitle={`from ${new URL(tx.merchantBaseUrl).hostname}`}
timestamp={tx.timestamp}
iconPath="/static/img/ri-hand-heart-line.svg"
pending={tx.pending}
></TransactionLayout>
);
case TransactionType.Refresh:
return (
<TransactionLayout
amount={tx.amountEffective}
debitCreditIndicator={"credit"}
title="Refresh"
subtitle={`via exchange ${tx.exchangeBaseUrl}`}
timestamp={tx.timestamp}
iconPath="/static/img/ri-refresh-line.svg"
pending={tx.pending}
></TransactionLayout>
);
}
}
function WalletHistory(props: any): JSX.Element {
@ -334,9 +478,11 @@ function WalletHistory(props: any): JSX.Element {
return <div>Loading ...</div>;
}
const txs = [...transactions.transactions].reverse();
return (
<div>
{transactions.transactions.map((tx) => (
{txs.map((tx) => (
<TransactionItem tx={tx} />
))}
</div>
@ -379,10 +525,7 @@ function WalletDebug(props: any): JSX.Element {
return (
<div>
<p>Debug tools:</p>
<button onClick={openExtensionPage("/popup.html")}>wallet tab</button>
<button onClick={openExtensionPage("/benchmark.html")}>benchmark</button>
<button onClick={openExtensionPage("/show-db.html")}>show db</button>
<button onClick={openExtensionPage("/tree.html")}>show tree</button>
<button onClick={openExtensionPage("/static/popup.html")}>wallet tab</button>
<br />
<button onClick={confirmReset}>reset</button>
<button onClick={reload}>reload chrome extension</button>

View File

@ -0,0 +1,15 @@
# Icon Copyright Info
## Remix Icons
https://github.com/Remix-Design/RemixIcon
Remix Icon is licensed under the Apache License Version 2.0. Feel free to use
these icons in your products and distribute them. We would be very grateful if
you mention "Remix Icon" in your product info, but it's not required. The only
thing we ask is that these icons are not for sale.
### Used Icons
* ri-*.svg

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2 20h20v2H2v-2zm2-8h2v7H4v-7zm5 0h2v7H9v-7zm4 0h2v7h-2v-7zm5 0h2v7h-2v-7zM2 7l10-5 10 5v4H2V7zm2 1.236V9h16v-.764l-8-4-8 4zM12 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/></svg>

After

Width:  |  Height:  |  Size: 297 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 15h2v2h-2v-2zm2-1.645V14h-2v-1.5a1 1 0 0 1 1-1 1.5 1.5 0 1 0-1.471-1.794l-1.962-.393A3.501 3.501 0 1 1 13 13.355zM15 4H5v16h14V8h-4V4zM3 2.992C3 2.444 3.447 2 3.999 2H16l5 5v13.993A1 1 0 0 1 20.007 22H3.993A1 1 0 0 1 3 21.008V2.992z"/></svg>

After

Width:  |  Height:  |  Size: 375 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 9a1 1 0 0 1 1 1 6.97 6.97 0 0 1 4.33 1.5h2.17c1.332 0 2.53.579 3.353 1.499L19 13a5 5 0 0 1 4.516 2.851C21.151 18.972 17.322 21 13 21c-2.79 0-5.15-.603-7.06-1.658A.998.998 0 0 1 5 20H2a1 1 0 0 1-1-1v-9a1 1 0 0 1 1-1h3zm1.001 3L6 17.021l.045.033C7.84 18.314 10.178 19 13 19c3.004 0 5.799-1.156 7.835-3.13l.133-.133-.12-.1a2.994 2.994 0 0 0-1.643-.63L19 15l-2.112-.001c.073.322.112.657.112 1.001v1H8v-2l6.79-.001-.034-.078a2.501 2.501 0 0 0-2.092-1.416L12.5 13.5H9.57A4.985 4.985 0 0 0 6.002 12zM4 11H3v7h1v-7zm9.646-7.425L14 3.93l.354-.354a2.5 2.5 0 1 1 3.535 3.536L14 11l-3.89-3.89a2.5 2.5 0 1 1 3.536-3.535zm-2.12 1.415a.5.5 0 0 0-.06.637l.058.069L14 8.17l2.476-2.474a.5.5 0 0 0 .058-.638l-.058-.07a.5.5 0 0 0-.638-.057l-.07.058-1.769 1.768-1.767-1.77-.068-.056a.5.5 0 0 0-.638.058z"/></svg>

After

Width:  |  Height:  |  Size: 924 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5.463 4.433A9.961 9.961 0 0 1 12 2c5.523 0 10 4.477 10 10 0 2.136-.67 4.116-1.81 5.74L17 12h3A8 8 0 0 0 6.46 6.228l-.997-1.795zm13.074 15.134A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12c0-2.136.67-4.116 1.81-5.74L7 12H4a8 8 0 0 0 13.54 5.772l.997 1.795z"/></svg>

After

Width:  |  Height:  |  Size: 396 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5.671 4.257c3.928-3.219 9.733-2.995 13.4.672 3.905 3.905 3.905 10.237 0 14.142-3.905 3.905-10.237 3.905-14.142 0A9.993 9.993 0 0 1 2.25 9.767l.077-.313 1.934.51a8 8 0 1 0 3.053-4.45l-.221.166 1.017 1.017-4.596 1.06 1.06-4.596 1.096 1.096zM13 6v2h2.5v2H10a.5.5 0 0 0-.09.992L10 11h4a2.5 2.5 0 1 1 0 5h-1v2h-2v-2H8.5v-2H14a.5.5 0 0 0 .09-.992L14 13h-4a2.5 2.5 0 1 1 0-5h1V6h2z"/></svg>

After

Width:  |  Height:  |  Size: 514 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M4 16V4H2V2h3a1 1 0 0 1 1 1v12h12.438l2-8H8V5h13.72a1 1 0 0 1 .97 1.243l-2.5 10a1 1 0 0 1-.97.757H5a1 1 0 0 1-1-1zm2 7a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm12 0a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg>

After

Width:  |  Height:  |  Size: 320 B