aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/Transaction.tsx')
-rw-r--r--packages/taler-wallet-webextension/src/wallet/Transaction.tsx386
1 files changed, 274 insertions, 112 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
index c6fa9ec68..5ed05f87f 100644
--- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
+++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx
@@ -18,6 +18,7 @@ import {
AbsoluteTime,
AmountJson,
Amounts,
+ ExtendedStatus,
Location,
MerchantInfo,
NotificationType,
@@ -60,11 +61,12 @@ import {
WarningBox,
} from "../components/styled/index.js";
import { Time } from "../components/Time.js";
-import { alertFromError } from "../context/alert.js";
+import { alertFromError, useAlertContext } from "../context/alert.js";
import { useBackendContext } from "../context/backend.js";
import { useTranslationContext } from "../context/translation.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js";
+import { SafeHandler } from "../mui/handlers.js";
import { Pages } from "../NavigationBar.js";
interface Props {
@@ -116,6 +118,12 @@ export function TransactionPage({
onSend={async () => {
null;
}}
+ onCancel={async () => {
+ await api.wallet.call(WalletApiOperation.AbortTransaction, {
+ transactionId,
+ });
+ goToWalletHistory(currency);
+ }}
onDelete={async () => {
await api.wallet.call(WalletApiOperation.DeleteTransaction, {
transactionId,
@@ -141,6 +149,7 @@ export function TransactionPage({
export interface WalletTransactionProps {
transaction: Transaction;
onSend: () => Promise<void>;
+ onCancel: () => Promise<void>;
onDelete: () => Promise<void>;
onRetry: () => Promise<void>;
onRefund: (id: string) => Promise<void>;
@@ -155,18 +164,29 @@ const PurchaseDetailsTable = styled.table`
}
`;
-export function TransactionView({
+type TransactionTemplateProps = Omit<
+ Omit<WalletTransactionProps, "onRefund">,
+ "onBack"
+> & {
+ children: ComponentChildren;
+};
+
+function TransactionTemplate({
transaction,
onDelete,
onRetry,
onSend,
- onRefund,
-}: WalletTransactionProps): VNode {
+ onCancel,
+ children,
+}: TransactionTemplateProps): VNode {
+ const { i18n } = useTranslationContext();
const [confirmBeforeForget, setConfirmBeforeForget] = useState(false);
+ const [confirmBeforeCancel, setConfirmBeforeCancel] = useState(false);
+ const { safely } = useAlertContext();
async function doCheckBeforeForget(): Promise<void> {
if (
- transaction.pending &&
+ transaction.extendedStatus === ExtendedStatus.Pending &&
transaction.type === TransactionType.Withdrawal
) {
setConfirmBeforeForget(true);
@@ -175,97 +195,64 @@ export function TransactionView({
}
}
- const SHOWING_RETRY_THRESHOLD_SECS = 30;
-
- const { i18n } = useTranslationContext();
-
- function TransactionTemplate({
- children,
- }: {
- children: ComponentChildren;
- }): VNode {
- const showSend = false;
- // (transaction.type === TransactionType.PeerPullCredit ||
- // transaction.type === TransactionType.PeerPushDebit) &&
- // !transaction.info.completed;
- const showRetry =
- transaction.error !== undefined ||
- transaction.timestamp.t_s === "never" ||
- (transaction.pending &&
- differenceInSeconds(new Date(), transaction.timestamp.t_s * 1000) >
- SHOWING_RETRY_THRESHOLD_SECS);
-
- return (
- <Fragment>
- <section style={{ padding: 8, textAlign: "center" }}>
- {transaction?.error ? (
- transaction.error.code === 7025 ? (
- <AlertView
- alert={{
- type: "warning",
- message: i18n.str`KYC check required for the transaction to complete`,
- description:
- transaction.error.kycUrl &&
- typeof transaction.error.kycUrl === "string" ? (
- <div>
- <i18n.Translate>
- Follow this link to the{` `}
- <a href={transaction.error.kycUrl}>KYC verifier</a>
- </i18n.Translate>
- </div>
- ) : (
- i18n.str`No more information has been provided`
- ),
- }}
- />
- ) : (
- <ErrorAlertView
- error={alertFromError(
- i18n.str`There was an error trying to complete the transaction`,
- transaction.error,
- )}
- />
- )
- ) : undefined}
- {transaction.pending && (
- <WarningBox>
- <i18n.Translate>This transaction is not completed</i18n.Translate>
- </WarningBox>
- )}
- </section>
- <section>{children}</section>
- <footer>
- <div>
- {showSend ? (
- <Button variant="contained" onClick={onSend}>
- <i18n.Translate>Send</i18n.Translate>
- </Button>
- ) : null}
- </div>
- <div>
- {showRetry ? (
- <Button variant="contained" onClick={onRetry}>
- <i18n.Translate>Retry</i18n.Translate>
- </Button>
- ) : null}
- <Button
- variant="contained"
- color="error"
- onClick={doCheckBeforeForget}
- >
- <i18n.Translate>Forget</i18n.Translate>
- </Button>
- </div>
- </footer>
- </Fragment>
- );
+ async function doCheckBeforeCancel(): Promise<void> {
+ setConfirmBeforeCancel(true);
}
- if (transaction.type === TransactionType.Withdrawal) {
- const total = Amounts.parseOrThrow(transaction.amountEffective);
- const chosen = Amounts.parseOrThrow(transaction.amountRaw);
- return (
- <TransactionTemplate>
+ const SHOWING_RETRY_THRESHOLD_SECS = 30;
+
+ const showSend = false;
+ // (transaction.type === TransactionType.PeerPullCredit ||
+ // transaction.type === TransactionType.PeerPushDebit) &&
+ // !transaction.info.completed;
+ const showRetry =
+ transaction.error !== undefined ||
+ transaction.timestamp.t_s === "never" ||
+ (transaction.extendedStatus === ExtendedStatus.Pending &&
+ differenceInSeconds(new Date(), transaction.timestamp.t_s * 1000) >
+ SHOWING_RETRY_THRESHOLD_SECS);
+
+ const transactionStillActive =
+ transaction.extendedStatus !== ExtendedStatus.Aborted &&
+ transaction.extendedStatus !== ExtendedStatus.Done &&
+ transaction.extendedStatus !== ExtendedStatus.Failed;
+ return (
+ <Fragment>
+ <section style={{ padding: 8, textAlign: "center" }}>
+ {transaction?.error ? (
+ transaction.error.code === 7025 ? (
+ <AlertView
+ alert={{
+ type: "warning",
+ message: i18n.str`KYC check required for the transaction to complete`,
+ description:
+ transaction.error.kycUrl &&
+ typeof transaction.error.kycUrl === "string" ? (
+ <div>
+ <i18n.Translate>
+ Follow this link to the{` `}
+ <a href={transaction.error.kycUrl}>KYC verifier</a>
+ </i18n.Translate>
+ </div>
+ ) : (
+ i18n.str`No more information has been provided`
+ ),
+ }}
+ />
+ ) : (
+ <ErrorAlertView
+ error={alertFromError(
+ i18n.str`There was an error trying to complete the transaction`,
+ transaction.error,
+ )}
+ />
+ )
+ ) : undefined}
+ {transaction.extendedStatus === ExtendedStatus.Pending && (
+ <WarningBox>
+ <i18n.Translate>This transaction is not completed</i18n.Translate>
+ </WarningBox>
+ )}
{confirmBeforeForget ? (
<Overlay>
<CenteredDialog>
@@ -282,18 +269,134 @@ export function TransactionView({
<Button
variant="contained"
color="secondary"
- onClick={async () => setConfirmBeforeForget(false)}
+ onClick={
+ (async () =>
+ setConfirmBeforeForget(false)) as SafeHandler<void>
+ }
>
<i18n.Translate>Cancel</i18n.Translate>
</Button>
- <Button variant="contained" color="error" onClick={onDelete}>
+ <Button
+ variant="contained"
+ color="error"
+ onClick={safely(
+ onDelete,
+ i18n.str`Could not forget transaction`,
+ )}
+ >
<i18n.Translate>Confirm</i18n.Translate>
</Button>
</footer>
</CenteredDialog>
</Overlay>
) : undefined}
+ {confirmBeforeCancel ? (
+ <Overlay>
+ <CenteredDialog>
+ <header>
+ <i18n.Translate>Caution!</i18n.Translate>
+ </header>
+ <section>
+ <i18n.Translate>
+ Doing a cancelation while the transaction still active might
+ result in lost coins. Do you still want to cancel the
+ transaction?
+ </i18n.Translate>
+ </section>
+ <footer>
+ <Button
+ variant="contained"
+ color="secondary"
+ onClick={
+ (async () =>
+ setConfirmBeforeCancel(false)) as SafeHandler<void>
+ }
+ >
+ <i18n.Translate>No</i18n.Translate>
+ </Button>
+
+ <Button
+ variant="contained"
+ color="error"
+ onClick={safely(
+ onCancel,
+ i18n.str`Could not cancel the active transaction`,
+ )}
+ >
+ <i18n.Translate>Yes</i18n.Translate>
+ </Button>
+ </footer>
+ </CenteredDialog>
+ </Overlay>
+ ) : undefined}
+ </section>
+ <section>{children}</section>
+ <footer>
+ <div>
+ {showSend ? (
+ <Button
+ variant="contained"
+ onClick={safely(onSend, i18n.str`Could not send`)}
+ >
+ <i18n.Translate>Send</i18n.Translate>
+ </Button>
+ ) : null}
+ </div>
+ <div>
+ {showRetry ? (
+ <Button
+ variant="contained"
+ onClick={safely(onRetry, i18n.str`Could not retry`)}
+ >
+ <i18n.Translate>Retry</i18n.Translate>
+ </Button>
+ ) : null}
+ {transactionStillActive ? (
+ <Button
+ variant="contained"
+ color="error"
+ onClick={doCheckBeforeCancel as SafeHandler<void>}
+ >
+ <i18n.Translate>Cancel</i18n.Translate>
+ </Button>
+ ) : (
+ <Button
+ variant="contained"
+ color="error"
+ onClick={doCheckBeforeForget as SafeHandler<void>}
+ >
+ <i18n.Translate>Forget</i18n.Translate>
+ </Button>
+ )}
+ </div>
+ </footer>
+ </Fragment>
+ );
+}
+
+export function TransactionView({
+ transaction,
+ onDelete,
+ onRetry,
+ onSend,
+ onRefund,
+ onCancel,
+}: WalletTransactionProps): VNode {
+ const { i18n } = useTranslationContext();
+ const { safely } = useAlertContext();
+
+ if (transaction.type === TransactionType.Withdrawal) {
+ const total = Amounts.parseOrThrow(transaction.amountEffective);
+ const chosen = Amounts.parseOrThrow(transaction.amountRaw);
+ return (
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Withdrawal`}
@@ -303,7 +406,8 @@ export function TransactionView({
{transaction.exchangeBaseUrl}
</Header>
- {!transaction.pending ? undefined : transaction.withdrawalDetails
+ {transaction.extendedStatus !==
+ ExtendedStatus.Pending ? undefined : transaction.withdrawalDetails
.type === WithdrawalType.ManualTransfer ? (
<Fragment>
<BankDetailsByPaytoType
@@ -418,7 +522,13 @@ export function TransactionView({
const total = Amounts.sub(price.effective, refund.effective).amount;
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
total={total}
@@ -491,7 +601,10 @@ export function TransactionView({
<div>
<Button
variant="contained"
- onClick={() => onRefund(transaction.proposalId)}
+ onClick={safely(
+ () => onRefund(transaction.proposalId),
+ i18n.str`Could not refund`,
+ )}
>
<i18n.Translate>Accept</i18n.Translate>
</Button>
@@ -529,7 +642,13 @@ export function TransactionView({
const total = Amounts.parseOrThrow(transaction.amountRaw);
const payto = parsePaytoUri(transaction.targetPaytoUri);
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Deposit`}
@@ -567,7 +686,13 @@ export function TransactionView({
).amount;
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Refresh`}
@@ -588,7 +713,13 @@ export function TransactionView({
const total = Amounts.parseOrThrow(transaction.amountEffective);
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Tip`}
@@ -613,7 +744,13 @@ export function TransactionView({
if (transaction.type === TransactionType.Refund) {
const total = Amounts.parseOrThrow(transaction.amountEffective);
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Refund`}
@@ -666,10 +803,10 @@ export function TransactionView({
return (
<div>
<QR text={text} />
- <Button onClick={copy}>
+ <Button onClick={copy as SafeHandler<void>}>
<i18n.Translate>copy</i18n.Translate>
</Button>
- <Button onClick={toggle}>
+ <Button onClick={toggle as SafeHandler<void>}>
<i18n.Translate>hide qr</i18n.Translate>
</Button>
</div>
@@ -678,10 +815,10 @@ export function TransactionView({
return (
<div>
<div>{text.substring(0, 64)}...</div>
- <Button onClick={copy}>
+ <Button onClick={copy as SafeHandler<void>}>
<i18n.Translate>copy</i18n.Translate>
</Button>
- <Button onClick={toggle}>
+ <Button onClick={toggle as SafeHandler<void>}>
<i18n.Translate>show qr</i18n.Translate>
</Button>
</div>
@@ -691,7 +828,13 @@ export function TransactionView({
if (transaction.type === TransactionType.PeerPullCredit) {
const total = Amounts.parseOrThrow(transaction.amountEffective);
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Credit`}
@@ -713,7 +856,8 @@ export function TransactionView({
text={transaction.exchangeBaseUrl as TranslatedString}
kind="neutral"
/>
- {transaction.pending /** pending is not-pay */ && (
+ {transaction.extendedStatus ===
+ ExtendedStatus.Pending /** pending is not-pay */ && (
<Part
title={i18n.str`URI`}
text={<ShowQrWithCopy text={transaction.talerUri} />}
@@ -738,7 +882,13 @@ export function TransactionView({
if (transaction.type === TransactionType.PeerPullDebit) {
const total = Amounts.parseOrThrow(transaction.amountEffective);
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Debit`}
@@ -777,7 +927,13 @@ export function TransactionView({
if (transaction.type === TransactionType.PeerPushDebit) {
const total = Amounts.parseOrThrow(transaction.amountEffective);
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Debit`}
@@ -824,7 +980,13 @@ export function TransactionView({
if (transaction.type === TransactionType.PeerPushCredit) {
const total = Amounts.parseOrThrow(transaction.amountEffective);
return (
- <TransactionTemplate>
+ <TransactionTemplate
+ transaction={transaction}
+ onDelete={onDelete}
+ onRetry={onRetry}
+ onSend={onSend}
+ onCancel={onCancel}
+ >
<Header
timestamp={transaction.timestamp}
type={i18n.str`Credit`}