using extendedStatus

This commit is contained in:
Sebastian 2023-01-13 16:09:33 -03:00
parent bc67ff0c7f
commit 0b2bf13def
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
8 changed files with 337 additions and 137 deletions

View File

@ -16,6 +16,7 @@
import {
AbsoluteTime,
Amounts,
ExtendedStatus,
NotificationType,
Transaction,
} from "@gnu-taler/taler-util";
@ -56,7 +57,9 @@ export function PendingTransactions({ goToTransaction }: Props): VNode {
const transactions =
!state || state.hasError
? cache.tx
: state.response.transactions.filter((t) => t.pending);
: state.response.transactions.filter(
(t) => t.extendedStatus === ExtendedStatus.Pending,
);
if (state && !state.hasError) {
cache.tx = transactions;

View File

@ -22,6 +22,7 @@ import {
Transaction,
TransactionType,
WithdrawalType,
ExtendedStatus,
} from "@gnu-taler/taler-util";
import { h, VNode } from "preact";
import { useTranslationContext } from "../context/translation.js";
@ -52,7 +53,7 @@ export function TransactionItem(props: { tx: Transaction }): VNode {
timestamp={AbsoluteTime.fromTimestamp(tx.timestamp)}
iconPath={"W"}
pending={
tx.pending
tx.extendedStatus === ExtendedStatus.Pending
? tx.withdrawalDetails.type ===
WithdrawalType.TalerBankIntegrationApi
? !tx.withdrawalDetails.confirmed

View File

@ -48,7 +48,14 @@ type Type = {
alerts: Alert[];
pushAlert: (n: Alert) => void;
removeAlert: (n: Alert) => void;
/**
*
* @param h
* @returns
* @deprecated use safely
*/
pushAlertOnError: <T>(h: (p: T) => Promise<void>) => SafeHandler<T>;
safely: <T>(h: (p: T) => Promise<void>, error: TranslatedString) => SafeHandler<T>;
};
const initial: Type = {
@ -56,6 +63,9 @@ const initial: Type = {
pushAlertOnError: () => {
throw Error("alert context not initialized");
},
safely: () => {
throw Error("alert context not initialized");
},
pushAlert: () => {
null;
},
@ -100,8 +110,18 @@ export const AlertProvider = ({ children }: Props): VNode => {
});
}
function safely<T>(
handler: (p: T) => Promise<void>,
message: TranslatedString
): SafeHandler<T> {
return withSafe(handler, (e) => {
const a = alertFromError(message, e);
pushAlert(a);
});
}
return h(Context.Provider, {
value: { alerts, pushAlert, removeAlert, pushAlertOnError },
value: { alerts, pushAlert, removeAlert, pushAlertOnError, safely },
children,
});
};

View File

@ -20,6 +20,7 @@ import { theme, Colors, rippleEnabled, rippleEnabledOutlined } from "./style";
// eslint-disable-next-line import/extensions
import { alpha } from "./colors/manipulation";
import { useState } from "preact/hooks";
import { SafeHandler } from "./handlers.js";
export const buttonBaseStyle = css`
display: inline-flex;
@ -55,6 +56,7 @@ interface Props {
tooltip?: string;
color?: Colors;
onClick?: () => Promise<void>;
// onClick?: SafeHandler<void>;
}
const button = css`

View File

@ -35,6 +35,7 @@ import * as popup from "./popup/index.stories.js";
import * as wallet from "./wallet/index.stories.js";
import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
import { AlertProvider } from "./context/alert.js";
function main(): void {
renderStories(
@ -56,28 +57,28 @@ function getWrapperForGroup(group: string): FunctionComponent {
case "popup":
return function PopupWrapper({ children }: any) {
return (
<Fragment>
<AlertProvider>
<PopupNavBar />
<PopupBox>{children}</PopupBox>
</Fragment>
</AlertProvider>
);
};
case "wallet":
return function WalletWrapper({ children }: any) {
return (
<Fragment>
<AlertProvider>
<LogoHeader />
<WalletNavBar />
<WalletBox>{children}</WalletBox>
</Fragment>
</AlertProvider>
);
};
case "cta":
return function WalletWrapper({ children }: any) {
return (
<Fragment>
<AlertProvider>
<WalletAction>{children}</WalletAction>
</Fragment>
</AlertProvider>
);
};
default:

View File

@ -556,9 +556,11 @@ function WalletTemplate({
{goToTransaction ? (
<PendingTransactions goToTransaction={goToTransaction} />
) : undefined}
<CurrentAlerts />
<WalletBox>
<AlertProvider>{children}</AlertProvider>
<AlertProvider>
<CurrentAlerts />
{children}
</AlertProvider>
</WalletBox>
</Fragment>
);

View File

@ -39,14 +39,13 @@ import {
WithdrawalDetails,
WithdrawalType,
} from "@gnu-taler/taler-util";
import { DevContextProviderForTesting } from "../context/devContext.js";
// import {
// createExample,
// createExampleWithCustomContext as createExampleInCustomContext,
// } from "../test-utils.js";
import { TransactionView as TestedComponent } from "./Transaction.js";
import { tests } from "@gnu-taler/web-util/lib/index.browser";
import beer from "../../static-dev/beer.png";
import { TransactionView as TestedComponent } from "./Transaction.js";
export default {
title: "transaction details",
@ -54,6 +53,7 @@ export default {
argTypes: {
onRetry: { action: "onRetry" },
onDelete: { action: "onDelete" },
onCancel: { action: "onCancel" },
onBack: { action: "onBack" },
},
};
@ -62,10 +62,10 @@ const commonTransaction = {
amountRaw: "KUDOS:11",
amountEffective: "KUDOS:9.2",
extendedStatus: ExtendedStatus.Done,
pending: false,
pending: undefined as any as boolean, //deprecated
timestamp: TalerProtocolTimestamp.now(),
transactionId: "txn:deposit:12",
frozen: false,
frozen: undefined as any as boolean, //deprecated
type: TransactionType.Deposit,
} as TransactionCommon;
@ -255,7 +255,7 @@ export const WithdrawFiveMinutesAgoAndPending = tests.createExample(
timestamp: TalerProtocolTimestamp.fromSeconds(
new Date().getTime() / 1000 - 60 * 5,
),
pending: true,
extendedStatus: ExtendedStatus.Pending,
},
}),
);
@ -295,7 +295,7 @@ export const WithdrawPendingManual = tests.createExample(
exchangePaytoUris: ["payto://iban/ES8877998399652238"],
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
} as WithdrawalDetails,
pending: true,
extendedStatus: ExtendedStatus.Pending,
},
}),
);
@ -311,7 +311,7 @@ export const WithdrawPendingTalerBankUnconfirmed = tests.createExample(
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
bankConfirmationUrl: "http://bank.demo.taler.net",
},
pending: true,
extendedStatus: ExtendedStatus.Pending,
},
},
);
@ -326,7 +326,7 @@ export const WithdrawPendingTalerBankConfirmed = tests.createExample(
confirmed: true,
reservePub: "A05AJGMFNSK4Q62NXR2FKNDB1J4EXTYQTE7VA4M9GZQ4TR06YBNG",
},
pending: true,
extendedStatus: ExtendedStatus.Pending,
},
},
);
@ -443,7 +443,10 @@ export const PaymentWithoutFee = tests.createExample(TestedComponent, {
});
export const PaymentPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.payment, pending: true },
transaction: {
...exampleData.payment,
extendedStatus: ExtendedStatus.Pending,
},
});
export const PaymentWithProducts = tests.createExample(TestedComponent, {
@ -540,7 +543,10 @@ export const DepositError = tests.createExample(TestedComponent, {
});
export const DepositPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.deposit, pending: true },
transaction: {
...exampleData.deposit,
extendedStatus: ExtendedStatus.Pending,
},
});
export const Refresh = tests.createExample(TestedComponent, {
@ -566,7 +572,7 @@ export const TipError = tests.createExample(TestedComponent, {
});
export const TipPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.tip, pending: true },
transaction: { ...exampleData.tip, extendedStatus: ExtendedStatus.Pending },
});
export const Refund = tests.createExample(TestedComponent, {
@ -581,7 +587,10 @@ export const RefundError = tests.createExample(TestedComponent, {
});
export const RefundPending = tests.createExample(TestedComponent, {
transaction: { ...exampleData.refund, pending: true },
transaction: {
...exampleData.refund,
extendedStatus: ExtendedStatus.Pending,
},
});
export const InvoiceCreditComplete = tests.createExample(TestedComponent, {
@ -591,7 +600,7 @@ export const InvoiceCreditComplete = tests.createExample(TestedComponent, {
export const InvoiceCreditIncomplete = tests.createExample(TestedComponent, {
transaction: {
...exampleData.pull_credit,
pending: true,
extendedStatus: ExtendedStatus.Pending,
},
});
@ -609,6 +618,6 @@ export const TransferDebitComplete = tests.createExample(TestedComponent, {
export const TransferDebitIncomplete = tests.createExample(TestedComponent, {
transaction: {
...exampleData.push_debit,
pending: true,
extendedStatus: ExtendedStatus.Pending,
},
});

View File

@ -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`}