diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet/Transaction.tsx')
-rw-r--r-- | packages/taler-wallet-webextension/src/wallet/Transaction.tsx | 386 |
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`} |