From 0b2bf13deff518c3ebbdaf4a72d2c162790906b7 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Fri, 13 Jan 2023 16:09:33 -0300
Subject: using extendedStatus

---
 .../src/wallet/Transaction.tsx                     | 386 +++++++++++++++------
 1 file changed, 274 insertions(+), 112 deletions(-)

(limited to 'packages/taler-wallet-webextension/src/wallet/Transaction.tsx')

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`}
-- 
cgit v1.2.3

