summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/components/HistoryItem.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/components/HistoryItem.tsx432
1 files changed, 432 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/components/HistoryItem.tsx b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
new file mode 100644
index 000000000..9be9326b2
--- /dev/null
+++ b/packages/taler-wallet-webextension/src/components/HistoryItem.tsx
@@ -0,0 +1,432 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+import {
+ AmountJson,
+ Amounts,
+ AmountString,
+ AbsoluteTime,
+ Transaction,
+ TransactionType,
+ WithdrawalType,
+ TransactionMajorState,
+ DenomLossEventType,
+ parsePaytoUri,
+} from "@gnu-taler/taler-util";
+import { h, VNode } from "preact";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Avatar } from "../mui/Avatar.js";
+import { Pages } from "../NavigationBar.js";
+import { assertUnreachable } from "../utils/index.js";
+import {
+ Column,
+ ExtraLargeText,
+ HistoryRow,
+ LargeText,
+ LightText,
+ SmallLightText,
+} from "./styled/index.js";
+import { Time } from "./Time.js";
+
+export function HistoryItem(props: { tx: Transaction }): VNode {
+ const tx = props.tx;
+ const { i18n } = useTranslationContext();
+ /**
+ *
+ */
+ switch (tx.type) {
+ case TransactionType.Withdrawal:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title={new URL(tx.exchangeBaseUrl).hostname}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"W"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? tx.withdrawalDetails.type ===
+ WithdrawalType.TalerBankIntegrationApi
+ ? !tx.withdrawalDetails.confirmed
+ ? i18n.str`Need approval in the Bank`
+ : i18n.str`Waiting for wire transfer to complete`
+ : tx.withdrawalDetails.type === WithdrawalType.ManualTransfer
+ ? i18n.str`Waiting for wire transfer to complete`
+ : "" //pending but no message
+ : undefined
+ }
+ />
+ );
+ case TransactionType.InternalWithdrawal:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title={new URL(tx.exchangeBaseUrl).hostname}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"I"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? tx.withdrawalDetails.type ===
+ WithdrawalType.TalerBankIntegrationApi
+ ? !tx.withdrawalDetails.confirmed
+ ? i18n.str`Need approval in the Bank`
+ : i18n.str`Exchange is waiting the wire transfer`
+ : tx.withdrawalDetails.type === WithdrawalType.ManualTransfer
+ ? i18n.str`Exchange is waiting the wire transfer`
+ : "" //pending but no message
+ : undefined
+ }
+ />
+ );
+ case TransactionType.Payment:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={tx.info.merchant.name}
+ subtitle={tx.info.summary}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"P"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? i18n.str`Payment in progress`
+ : undefined
+ }
+ />
+ );
+ case TransactionType.Refund:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ subtitle={tx.paymentInfo ? tx.paymentInfo.summary : undefined} //FIXME: DD37 wallet-core is not returning this value
+ title={
+ tx.paymentInfo
+ ? tx.paymentInfo.merchant.name
+ : "--unknown merchant--"
+ } //FIXME: DD37 wallet-core is not returning this value
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"R"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? i18n.str`Executing refund...`
+ : undefined
+ }
+ />
+ );
+ case TransactionType.Refresh:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title={"Refresh"}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"R"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? i18n.str`Refreshing coins...`
+ : undefined
+ }
+ />
+ );
+ case TransactionType.Deposit:{
+ const payto = parsePaytoUri(tx.targetPaytoUri);
+ const title = payto === undefined || !payto.isKnown ? tx.targetPaytoUri :
+ payto.params["receiver-name"] ;
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={title}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"D"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? i18n.str`Deposit in progress`
+ : undefined
+ }
+ />
+ );
+ }
+ case TransactionType.PeerPullCredit:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title={tx.info.summary || "Invoice"}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"I"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? i18n.str`Waiting to be paid`
+ : undefined
+ }
+ />
+ );
+ case TransactionType.PeerPullDebit:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={tx.info.summary || "Invoice"}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"I"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? i18n.str`Payment in progress`
+ : undefined
+ }
+ />
+ );
+ case TransactionType.PeerPushCredit:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"credit"}
+ title={tx.info.summary || "Transfer"}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"T"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? i18n.str`Receiving the transfer`
+ : undefined
+ }
+ />
+ );
+ case TransactionType.PeerPushDebit:
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={tx.info.summary || "Transfer"}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"T"}
+ currentState={tx.txState.major}
+ description={
+ tx.txState.major === TransactionMajorState.Pending
+ ? i18n.str`Waiting to be received`
+ : undefined
+ }
+ />
+ );
+ case TransactionType.DenomLoss: {
+ switch (tx.lossEventType) {
+ case DenomLossEventType.DenomExpired: {
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={i18n.str`Denomination expired`}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"L"}
+ currentState={tx.txState.major}
+ description={undefined}
+ />
+ );
+ }
+ case DenomLossEventType.DenomVanished: {
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={i18n.str`Denomination vanished`}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"L"}
+ currentState={tx.txState.major}
+ description={undefined}
+ />
+ );
+ }
+ case DenomLossEventType.DenomUnoffered: {
+ return (
+ <Layout
+ id={tx.transactionId}
+ amount={tx.amountEffective}
+ debitCreditIndicator={"debit"}
+ title={i18n.str`Denomination unoffered`}
+ timestamp={AbsoluteTime.fromPreciseTimestamp(tx.timestamp)}
+ iconPath={"L"}
+ currentState={tx.txState.major}
+ description={undefined}
+ />
+ );
+ }
+ default: {
+ assertUnreachable(tx.lossEventType);
+ }
+ }
+ break;
+ }
+ case TransactionType.Recoup:
+ throw Error("recoup transaction not implemented");
+ default: {
+ assertUnreachable(tx);
+ }
+ }
+}
+
+function Layout(props: LayoutProps): VNode {
+ const { i18n } = useTranslationContext();
+ return (
+ <HistoryRow
+ href={Pages.balanceTransaction({ tid: props.id })}
+ style={{
+ 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",
+ }}
+ >
+ <Avatar
+ style={{
+ border: "solid gray 1px",
+ color: "gray",
+ boxSizing: "border-box",
+ }}
+ >
+ {props.iconPath}
+ </Avatar>
+ <Column>
+ <LargeText>
+ <div>{props.title}</div>
+ {props.subtitle && (
+ <div style={{ color: "gray", fontSize: "medium", marginTop: 5 }}>
+ {props.subtitle}
+ </div>
+ )}
+ </LargeText>
+ {props.description && (
+ <LightText style={{ marginTop: 5, marginBottom: 5 }}>
+ <i18n.Translate>{props.description}</i18n.Translate>
+ </LightText>
+ )}
+ <SmallLightText style={{ marginTop: 5 }}>
+ <Time timestamp={props.timestamp} format="HH:mm" />
+ </SmallLightText>
+ </Column>
+ <TransactionAmount
+ currentState={props.currentState}
+ amount={Amounts.parseOrThrow(props.amount)}
+ debitCreditIndicator={props.debitCreditIndicator}
+ />
+ </HistoryRow>
+ );
+}
+
+interface LayoutProps {
+ debitCreditIndicator: "debit" | "credit" | "unknown";
+ amount: AmountString | "unknown";
+ timestamp: AbsoluteTime;
+ title: string;
+ subtitle?: string;
+ id: string;
+ iconPath: string;
+ currentState: TransactionMajorState;
+ description?: string;
+}
+
+interface TransactionAmountProps {
+ debitCreditIndicator: "debit" | "credit" | "unknown";
+ amount: AmountJson;
+ currentState: TransactionMajorState;
+}
+
+function TransactionAmount(props: TransactionAmountProps): VNode {
+ const { i18n } = useTranslationContext();
+ let sign: string;
+ switch (props.debitCreditIndicator) {
+ case "credit":
+ sign = "+";
+ break;
+ case "debit":
+ sign = "-";
+ break;
+ case "unknown":
+ sign = "";
+ }
+ return (
+ <Column
+ style={{
+ textAlign: "center",
+ color:
+ props.currentState !== TransactionMajorState.Done
+ ? "gray"
+ : sign === "+"
+ ? "darkgreen"
+ : sign === "-"
+ ? "darkred"
+ : undefined,
+ }}
+ >
+ <ExtraLargeText>
+ {sign}
+ {Amounts.stringifyValue(props.amount, 2)}
+ </ExtraLargeText>
+ {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>
+ );
+}